Updated state in useEffect returns in first render.
enesozturk opened this issue · 3 comments
Hi, before create an issue, I checked previous issues and cannot find same issue except related ones.
I have and simple custom hook that stores boolean (true) in state and an useEffect that changes state to false. When I render my hook with renderHook and check returned value, received value is updated value. I cannot catch the second render, renderHook returns updated value in the first run.
react-hooks-testing-libraryversion: ^7.0.2reactversion: ^16.13.0,react-domversion (if applicable): 16.8.4,react-test-rendererversion (if applicable):nodeversion: 14.17.6npm(oryarn) version: 2.5.1 (yarn)
Relevant code or config:
useCustomState.js (temp name):
import { useEffect, useState } from "react";
export default function useCustomState() {
const [seen, setSeen] = useState(true);
useEffect(() => {
setSeen(false);
}, []);
return { seen };
}useCustomState.test.js:
import {renderHook} from '@testing-library/react-hooks/native';
import {storageService} from '../../services';
import useCustomState from '../useCustomState';
describe('useCustomState', () => {
it('should return data from store correctly', async () => {
const {result} = renderHook(() =>
useCustomState({
textId: 'web_platform.top_notification.design_transition',
storageKey: storageService.keys.designTransition,
}),
);
/**
* 🔴 Fails here
* Expected: true
* Received: false
*/
expect(result.current.seen).toEqual(true);
await waitForNextUpdate();
expect(result.current.seen).toEqual(false);
});
});What you did:
Tried to check state value returned from my custom hook that changes in useEffect.
What happened:
Received updated value in the first render (first run).
Hi @enesozturk,
The reason this is happening is because renderHook wraps the underlying render call in act (react spits out warnings if we don't). I think the theory around that is that the resulting state from a synchronous effect like this is the first observable state the user will see.
Generally, I would recommend not getting too caught up on the transient state changes and focus on the result.current at each observable stage of your hooks lifecycle as the initial state and any other in between states are essentially implementation details.
That said, there are always exceptions so we do capture all state changes from the internal renders in result.all if you want do need to all state changes.
describe('useCustomState', () => {
it('should return data from store correctly', async () => {
const {result} = renderHook(() =>
useCustomState({
textId: 'web_platform.top_notification.design_transition',
storageKey: storageService.keys.designTransition,
}),
);
expect(result.all[0].seen).toEqual(true);
expect(result.all[1].seen).toEqual(false);
});
});Hope that helps.
The solution helps to pass tests. But the logic behind the renderHook and your recommendation will be in my attention. Thanks.
Also maybe it is worth mentioning for future visitors; I was checking localStorage in useEffect to set state. Just cleared my hook by removing it and other functions inside.
Hey @mpeyper! Is there a way to do the same thing for a component? I want to test the initial render to be sure that there are no errors. Example component:
import { useEffect, useState } from "react";
const MyComponent = () => {
const [counter, setCounter] = useState(0);
useEffect(() => {
setCounter(1);
}, []);
return <div>{counter}</div>;
}Here <div>1</div> will be rendered and there is no way (that I know of) to get the initial component state.
I can open a separate ticket for that.