Can I set state inside a useEffect hook
Solution 1:
Generally speaking, using setState
inside useEffect
will create an infinite loop that most likely you don't want to cause. There are a couple of exceptions to that rule which I will get into later.
useEffect
is called after each render and when setState
is used inside of it, it will cause the component to re-render which will call useEffect
and so on and so on.
One of the popular cases that using useState
inside of useEffect
will not cause an infinite loop is when you pass an empty array as a second argument to useEffect
like useEffect(() => {....}, [])
which means that the effect function should be called once: after the first mount/render only. This is used widely when you're doing data fetching in a component and you want to save the request data in the component's state.
Solution 2:
For future purposes, this may help too:
It's ok to use setState in useEffect
you just need to have attention as described already to not create a loop.
But it's not the only problem that may occur. See below:
Imagine that you have a component Comp
that receives props
from parent and according to a props
change you want to set Comp
's state. For some reason, you need to change for each prop in a different useEffect
:
DO NOT DO THIS
useEffect(() => {
setState({ ...state, a: props.a });
}, [props.a]);
useEffect(() => {
setState({ ...state, b: props.b });
}, [props.b]);
It may never change the state of a as you can see in this example: https://codesandbox.io/s/confident-lederberg-dtx7w
The reason why this happen in this example it's because both useEffects run in the same react cycle when you change both prop.a
and prop.b
so the value of {...state}
when you do setState
are exactly the same in both useEffect
because they are in the same context. When you run the second setState
it will replace the first setState
.
DO THIS INSTEAD
The solution for this problem is basically call setState
like this:
useEffect(() => {
setState(state => ({ ...state, a: props.a }));
}, [props.a]);
useEffect(() => {
setState(state => ({ ...state, b: props.b }));
}, [props.b]);
Check the solution here: https://codesandbox.io/s/mutable-surf-nynlx
Now, you always receive the most updated and correct value of the state when you proceed with the setState
.
I hope this helps someone!
Solution 3:
Effects are always executed after the render phase is completed even if you setState inside the one effect, another effect will read the updated state and take action on it only after the render phase.
Having said that its probably better to take both actions in the same effect unless there is a possibility that b
can change due to reasons other than changing a
in which case too you would want to execute the same logic