I was learning useEffect
hook of React recently, and I tried to write a function with useState
and useEffect
to see how different kinds of useEffect
dependencies works. Below is my code:
import { useEffect, useState } from "react";
import "./App.css";
function App() {
const [st1, setSt1] = useState("");
const [st2, setSt2] = useState("");
console.log("I'm in...");
useEffect(() => {
console.log("ef1");
});
useEffect(() => {
console.log("ef2");
setSt1("st1");
}, []);
useEffect(() => {
console.log("ef3");
setSt2("st2");
}, [st1]);
useEffect(() => {
console.log("ef4");
}, [st1, st2]);
return (
<div className="App">
<header className="App-header"></header>
</div>
);
}
export default App;
In my expectation, first time would run every useEffect
setup functions, then the setState
would cause the re-render and run the ef1
、ef3
、ef4
,then the ef3
setState
would cause the last re-render and run ef1
. So my expected output is
I'm in...
ef1
ef2
ef3
ef4
I'm in...
ef1
// check st1, different, execute ef3
ef3
// check st2, different, execute ef4
ef4
I'm in...
ef1
// check st2, same as previous value, doing nothing
but when I run the code, the real output is
I'm in...
ef1
ef2
ef3
ef4
I'm in...
ef1
ef3
ef4
I'm in...
I don’t understand why it doesn’t execute the ef1
function in last render.
Can someone explain this to me? Thanks
2
Answers
That’s because
useEffect
with dependencies array equalsundefined
would run on every rerender. Basically when you dosetState
you trigger a mutation (e.g. rerender). Since:On your third render you don’t trigger any mutation in
ef4
so youref1
use effect is not triggered.After your first render, all your effects would get executed, giving:
React will batch the state updates that happened in your useEffects, so when React rerenders and executes your component for the second time, both
st1
andst2
will now have their updated values in them. As bothst1
andst2
have changed, your 3rd and 4th effects will run (as well as the first as no dependency was provided), giving:The state update in your third effect doesn’t change the state, so we should be all done with the logging. However, as you’re calling the set state hook with the same value, React will bail out of the next "render", and React may need to render your component an additional time before it bails out (to check the render output hasn’t changed). When React calls your component for the third time you get:
In this case, React called your component but didn’t commit anything as nothing has changed, so no effects are run. From the above docs link on bailing out:
The bailing out / "extra" rerender detail is a bit of an implementation detail, and typically you don’t need to worry about it since your React components should be pure, however, if you want to, you can read a bit more about the bailing out behaviour in this answer from Dan Abramov, this post of his, or this other answer, or this post.