Skip to content

Better error handling for hydrate in non-browser environment #796

Open
@jsjoeio

Description

@jsjoeio
  • react-hooks-testing-library version: v8.0.0-alpha.1 - created a commit starting here
  • react version: N/A
  • react-dom version (if applicable): N/A
  • react-test-renderer version (if applicable): N/A
  • node version: v14.17.6
  • npm (or yarn) version: v1.22.17

Relevant code or config:

See below

What you did:

I added a test that runs in a Node environment to see if #605 was fixed (sidenote: using this issue/solution in a TypeScript course I'm authoring).

What happened:

I thought the solution would allow renderHook to be called in SSR environments but we get the same issue.

Summary of all failing tests
 FAIL  src/server/__tests__/ssr.test.ts
   ssr  should not throw ReferenceError

    The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/en/configuration#testenvironment-string.
    Consider using the "jsdom" test environment.
    
    ReferenceError: document is not defined

      32 |         throw new Error('The component can only be hydrated once')
      33 |       } else {
    > 34 |         container = document.createElement('div')
         |         ^
      35 |         container.innerHTML = serverOutput
      36 |         act(() => {
      37 |           ReactDOM.hydrate(testHarness(renderProps), container!)

      at hydrate (src/server/pure.ts:34:9)
      at Object.<anonymous> (src/server/__tests__/ssr.test.ts:20:5)


Test Suites: 1 failed, 61 passed, 62 total
Tests:       1 failed, 204 passed, 205 total
Snapshots:   0 total
Time:        7.859 s
Ran all test suites.

Note: I added the original author on Discord to try and contact them about this PR. I saw in the comments they said they tested in a Node environment so I may be doing something differently.

Reproduction:

  1. git clone https://github.com/testing-library/react-hooks-testing-library.git
  2. touch src/server/__tests__/ssr.test.ts
  3. Add this code:
/**
 * @jest-environment node
 */
import { useState } from 'react'
import { renderHook, act } from '..'

// This verifies that renderHook can be called in
// a SSR-like environment.
describe('ssr', () => {
  function useLoading() {
    if (typeof window !== 'undefined') {
      const [loading, setLoading] = useState(false)
      return { loading, setLoading }
    }
  }

  test('should not throw ReferenceError', () => {
    const { result, hydrate } = renderHook(() => useLoading())

    hydrate()

    act(() => result?.current?.setLoading(true))

    expect(result?.current?.loading).toBe(true)
  })
})
  1. Run yarn test --watch=false

Problem description:

Even though #607 modified src/server/pure.ts to fill the container with the ReactDOM tree in hydrate(), this still assumes document is always available.

Suggested solution:

I see three potential solutions.

Option 1: add DOM to test before hydrate

Based on my understanding, hydrate is supposed to be called on the client. So in our test, we could create a mock document and make sure it's available globally before calling hydrate. I see this more like a bandaid though.

Option 2: check if document is defined

Add a check to see if document is available before using it.

    hydrate() {
      if (container) {
        throw new Error('The component can only be hydrated once')
      } else {
        if (typeof document !== 'undefined') {
          container = document.createElement('div')
          container.innerHTML = serverOutput
          act(() => {
            ReactDOM.hydrate(testHarness(renderProps), container!)
          })
        }
      }
    },

Option 3: pass in document

This is the approach I usually take, but it would be a breaking change and most people don't like passing in globals this way.

hydrate(_document: Document) {
// ...
}

// Then to call it 
hydrate(document)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions