I am trying to figure out when useEffect causes a re-render. I am very surprised by the result of the following example:
https://codesandbox.io/embed/romantic-sun-j5i4m
function useCounter(arr = [1, 2, 3]) { const [counter, setCount] = useState(0); useEffect(() => { for (const i of arr) { setCount(i); console.log(counter); } }, [arr]); } function App() { useCounter(); console.log("render"); return <div className="App" />; }
The result of this example is as follows:
I don't know why:
setCount
+ one initial render - so 4 times)Changing state will always cause a re-render. By default, useEffect always runs after render has run. This means if you don't include a dependency array when using useEffect to fetch data, and use useState to display it, you will always trigger another render after useEffect runs.
Every state change in a hook, whether it affects its return value or not, will cause the “host” component to re-render.
Inside, useEffect compares the two objects, and since they have a different reference, it once again fetches the users and sets the new user object to the state. The state updates then triggers a re-render in the component.
You can add a condition in the call back function that checks if a certain condition is met, e.g. if data is empty. If it is empty, then fetch data, otherwise do nothing. This will prevent the infinite loop from happening. const getData = useEffect(()=>{ const fetchData = () => { UserService.
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.
useEffect
is called after the mounting.useEffect
will 'save' the initial state and thus counter
will be 0 whenever refered to inside it.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.useEffect
and instead waits till the useEffect
is done to re-render.useEffect
is done, React remembers that the state of counter
has changed during its execution, thus it will re-render the App.useCounter
is called again. Note here that no parameters are passed to the useCounter
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 the arr
is seen as new, the useEffect
hook will run again. This is the only reason I can explain the useEffect
running a second time. useEffect
, the counter
will have the value of 3. The console log will thus log the number 3 three times as expected.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.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 the useEffect
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 its counter
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With