I have the following useEffects in a React functional component below. I want to be sure that when showComponentProp is updated, that the first useEffect updates the state before the second useEffect is run.
I understand that useEffect hooks are run in the order they appear in the component body, however, as these contain setState functions, I’m not 100% sure of the behaviour. I don’t want there to be a race condition between the two setState functions in the useEffect hooks.
Could someone please clarify the exact order of the useEffects being called and when the component is rerendered in this case. For instance, is the first effect called, and when the isVisible state is updated, does the component rerender before the second effect is called? In that case will both effects be run again after the rerender, or just the second effect as the first effect was called previously.
Any references would be appreciated as I could not find a resource that explained this.
const [isVisible, setIsVisible] = useState(showComponentProp);
useEffect(() => {
setIsVisible(showComponentProp);
}, [showComponentProp]);
useEffect(() => {
if (showComponentProp && timerProp) {
const timeout = setTimeout(() => setIsVisible(false), timerProp);
return () => clearTimeout(timeout);
}
}, [showComponentProp, timerProp]);
2
Answers
The first
useEffect
hook’s callback will certainly enqueue theisVisible
state update before the seconduseEffect
hook is called.All React hooks are called each render cycle, in the order they are defined. If
showComponentProp
changes, then because it is included in the dependency array of bothuseEffect
hooks it will trigger the bothuseEffect
hooks’ callbacks, in the order they are defined.useEffect
hook callbacks will be called, in order.isVisible
state update to update to the currentshowComponentProp
value.showComponentProp
andtimerProp
values, and if both are truthy enter the if-block and instantiate the timeout to enqueue anotherisVisible
state update to false, and return auseEffect
hook cleanup function.timerProp
ms and calls the callback to enqueue anotherisVisible
state update to false, and trigger another component rerender, e.g. back to step 1 above.In any case, both
useEffect
hooks will be called each and every render cycle, and the callbacks called only when the dependencies change.However, I suspect you could accomplish this all in a single
useEffect
hook, which may make understanding the logic and flow a bit easier.No, the reason is that the dependency array dictates the scope of the code inside the hook. At present neither hook has set setIsVisible as its dependency.
More clearly, at present the first hook will run only on the following events:
Similarly the second hook will run only on the following events:
No, each render will be followed by exactly only one useEffect event, however, there may be any number of useEffect handlers. All will execute in the order of the code. Each state setter call across all useEffect handlers will be queued up, and will process only on the next single render.
yes, It is so, both effects will run after every single render,
however the scope is determined by the dependency array, as mentioned earlier.
No, this is not the case, which you may understand by now.
Please see below, a sample code is given along with its test run results.
App.js
Test runs
Application instance:
Test 1 : When the App has been loaded initially, the following log entries have been created.
Please note the additional logs created on StrictMode
have been removed for brevity.
Notes :
useEffect only has taken the effect. Since its
value is false. Therefore the latest render has got the same false value as shown in the log.
The summary is that there are two renders, two useEffect calls and only one state setter call on loading the app.
Test 2 : Now clicking on the Toggle Visibility button
Logs generated:
Notes :
The component rendered since there is a state change within the component. However, there is no useEffect invoked this time. The logs
do not show anything from it. Since the dependency does not include the state isVisible, no useEffect fired. This is the point with respect to your first question.
The summary is that there is only one render, no useEffects calls and no state setter calls in this test.
Test 3 : Now clicking on Toggle Show componentPro
Logs generated:
Notes:
The component rendered since there is a change in the parent component.
As a result, upon rendering, the two useEffects have been invoked since its dependency include showComponentProp, and there is a change in
that prop.
The state setter call in the first useEffect have an effect since the value to update and the present value of the state isVisible are different. It means there is a change in the state.
However, the next state setter in the second
useEffect has no effect. Since its
value is false and the state isVisible is also false. So there is no change in value.
Your second and third questions should have been answered here.
The summary is that there are two renders, two useEffects invoked and there is only one state setter call.
Test 4 – Clicking on button Increase TimerProp by 10 milliseconds
The Logs:
Notes:
The component rendered since there is a change in the parent component.
As a result, upon rendering, the first useEffect did not fire since its dependency does not match.
The second useEffect have been invoked since its dependency includes timerProp, and there is a change in that prop. However there is no state update in isVisible since there is no change in the value, its has false already, and the state setter in the second useEffect is also trying to update it with the same false value.
The summary is that there is one render, one useEffect invoked and there is no state setter call.