reactjsreact-hooks

will useEffect setup function run every time re-render if not setting the dependencies?


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 ef1ef3ef4,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


Solution

  • After your first render, all your effects would get executed, giving:

    I'm in...
    ef1
    ef2
    ef3
    ef4
    

    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 and st2 will now have their updated values in them. As both st1 and st2 have changed, your 3rd and 4th effects will run (as well as the first as no dependency was provided), giving:

    I'm in...
    ef1
    ef3
    ef4
    

    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:

    I'm in...
    

    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:

    React will bail out without rendering the children or firing effects

    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.