You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sometimes a hook relies on the props passed to it in order to do it's thing. For example the `useCounter` hook we built in the [Basic Hooks](/usage/basic-hooks) section could easily accept the initial value of the counter:
11
+
Often, a hook is going to need a value out of context. The `useContext` hook is really good for this, but it will ofter required a `Provider` to be wrapped around the component using the hook. We can use the `wrapper` option for `renderHook` to do just that.
12
+
13
+
Let's change the `useCounter` example from the [Basic Hooks section](/usage/basic-hooks) to get a `step` value from context and build a `CounterStepProvider` that allows us to set the value:
test('should increment counter from custom initial value', () => {
30
-
const { result } =renderHook(() =>useCounter(9000))
39
+
test('should use custom step when incrementing', () => {
40
+
constwrapper= ({ children }) =><CounterStepProvider step={2}>{children}</CounterStepProvider>
41
+
const { result } =renderHook(() =>useCounter(), { wrapper })
31
42
32
43
act(() => {
33
44
result.current.increment()
34
45
})
35
46
36
-
expect(result.current.count).toBe(9001)
47
+
expect(result.current.count).toBe(2)
37
48
})
38
49
```
39
50
40
-
### Changing Props
51
+
The `wrapper` option will accept any React component, but it **must** render `children` in order for the test component to render and the hook to execute.
41
52
42
-
Many of the hook primitives use an array of dependent values to determine when to perform specific actions, such as recalculating an expensive value or running an effect. If we update our `useCounter` hook to have a `reset` function that resets the value to the `initialValue` it might look something like this:
53
+
### ESLint Warning
54
+
55
+
It can be very tempting to try to inline the `wrapper` variable into the `renderHook` line, and there is nothing technically wrong with doing that, but if you are using [`eslint`](https://eslint.org/) and [`eslint-plugin-react`](https://github.com/yannickcr/eslint-plugin-react), you will see a linting error that says:
56
+
57
+
> Component definition is missing display name
58
+
59
+
This is caused by the `react/display-name` rule and although it's unlikely to cause you any issues, it's best to take steps to remove it. If you feel strongly about not having a seperate `wrapper` variable, you can disable the error for the test file but adding a special comment to the top of the file:
test('should use custom step when incrementing', () => {
68
+
const { result } =renderHook(() =>useCounter(), {
69
+
wrapper: ({ children }) =><CounterStepProvider step={2}>{children}</CounterStepProvider>
70
+
})
71
+
72
+
act(() => {
73
+
result.current.increment()
74
+
})
75
+
76
+
expect(result.current.count).toBe(2)
77
+
})
78
+
```
79
+
80
+
Similar techniques can be used to disable the error for just the specific line, or for the whole project, but please take the time to understand the impact that disabling linting rules will have on you, your team, and your project.
81
+
82
+
## Async
83
+
84
+
Sometimes, a hook can trigger asynchronous updates that will not be immediately reflected in the `result.current` value. Luckily, `renderHook` returns a utility that allows the test to wait for the hook to update using `async/await` (or just promise callbacks if you prefer) called `waitForNextUpdate`.
85
+
86
+
Let's further extend `useCounter` to have an `incrementAsync` callback that will update the `count` after `100ms`:
Now, the only time the `reset` function will be updated is if `initialValue` changes. The most basic way to handle changing the input props of our hook in a test is to simply update the value in a variable and rerender the hook:
101
+
To test `incrementAsync` we need to `await waitForNextUpdate()` before the make our assertions:
This is fine, but if there are lots of props, it can become a bit difficult to have variables to keep track of them all. Another option is to use the `initialProps` option and `newProps` of `rerender`:
118
+
### Suspense
119
+
120
+
`waitForNextUpdate` will also wait for hooks that suspends using [React's `Suspense`](https://reactjs.org/docs/code-splitting.html#suspense) functionality finish rendering.
121
+
122
+
### `act` Warning
123
+
124
+
When testing async hooks, you will likely see a warning from React that tells you to wrap the update in `act(() => {...})`, but you can't because the update is internal to the hook code, not the test code. This is a [known issue](https://github.com/mpeyper/react-hooks-testing-library/issues/14) and should have a fix when React `v16.9.0` is released, but until then, you can either just ignore the warning, or suppress the output:
Another case where this is useful is when you want limit the scope of the variables being closed over to just be inside the hook callback. The following (contrived) example fails because the `id` value changes for both the setup and cleanup of the `useEffect` call:
148
+
## Errors
149
+
150
+
If you need to test that a hook throws the errors you expect it to, you can use `result.error` to access an error that may have been thrown in the previous render. For example, we could make the `useCounter` hook threw an error if the count reached a specific value:
By using the `initialProps` and `newProps` the captured `id` value from the first render is used to clean up the effect, allowing the test to pass as expected:
124
-
125
170
```js
126
-
import { useEffect } from 'react'
127
-
import { renderHook } from "react-hooks-testing-library"
128
-
import sideEffect from './sideEffect
129
-
130
-
test("should clean up side effect", () => {
131
-
const { rerender } =renderHook(
132
-
({ id }) => {
133
-
useEffect(() => {
134
-
sideEffect.start(id)
135
-
return () => {
136
-
sideEffect.stop(id) // this id will get the new value when the effect is cleaned up
0 commit comments