useEffect Hook Example: What causes the re-render?
I'm going to do my best to explain(or walk through) what is happening. I'm also making two assumptions, in point 7 and point 10.
- App component mounts.
useEffect
is called after the mounting.useEffect
will 'save' the initial state and thuscounter
will be 0 whenever refered to inside it.- The loop runs 3 times. Each iteration
setCount
is called to update the count and the console log logs the counter which according to the 'stored' version is 0. So the number 0 is logged 3 times in the console. Because the state has changed (0 -> 1, 1 -> 2, 2 -> 3) React sets like a flag or something to tell itself to remember to re-render. - React has not re-rendered anything during the execution of
useEffect
and instead waits till theuseEffect
is done to re-render. - Once the
useEffect
is done, React remembers that the state ofcounter
has changed during its execution, thus it will re-render the App. - The app re-renders and the
useCounter
is called again. Note here that no parameters are passed to theuseCounter
custom hook. Asumption: I did not know this myself either, but I think the default parameter seems to be created again, or atleast in a way that makes React think that it is new. And thus because thearr
is seen as new, theuseEffect
hook will run again. This is the only reason I can explain theuseEffect
running a second time. - During the second run of
useEffect
, thecounter
will have the value of 3. The console log will thus log the number 3 three times as expected. - After the
useEffect
has run a second time React has found that the counter changed during execution (3 -> 1, 1 -> 2, 2 -> 3) and thus the App will re-render causing the third 'render' log. - Asumption: because the internal state of the
useCounter
hook did not change between this render and the previous from the point of view of the App, it does not execute code inside it and thus theuseEffect
is not called a third time. So the first render of the app it will always run the hook code. The second one the App saw that the internal state of the hook changed itscounter
from 0 to 3 and thus decides to re-run it, and the third time the App sees the internal state was 3 and is still 3 so it decides not to re-run it. That's the best reason I can come up with for the hook to not run again. You can put a log inside the hook itself to see that it does not infact run a third time.
This is what I see happening, I hope this made it a little bit clearer.
I found an explanation for the third render in the react docs. I think this clarifies why react does the third render without applying the effect:
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
It seems that useState and useReducer share this bail out logic.
setState and similar hooks do not immediately rerender your component. They may batch or defer the update until later. So you get only one rerender after the latest setCount
with counter === 3
.
You get initial render with counter === 0
and two additional rerenders with counter === 3
. I am not sure why it doesn't go to an infinite loop. arr = [1, 2, 3]
should create a new array on every call and trigger useEffect
:
- initial render sets
counter
to0
useEffect
logs0
three times, setscounter
to3
and triggers a rerender- first rerender with
counter === 3
useEffect
logs3
three times, setscounter
to3
and ???
React should either stop here or go to an infinite loop from step 3.