useEffect is one of those React/Preact hooks that most people have a love/hate relationship with, but like it or not, it's good to understand how i...
Some comments have been hidden by the post's author - find out more
For further actions, you may consider blocking this person and/or reporting abuse
The title is very misleading as the article doesn't explain when
useEffectis called...Not to mention there are couple points I'm pretty sure are wrong in the article...
When
useEffectis called?The real moment when
useEffectis called is after every render.==> AFTER not during <===
But for a better understanding of how things fits together, here's a rundown of how it actually works...
Phase 1: React Render
React renders the component in memory, meaning it executes the function or class component to know what it will produce in output.
Phase 2: React Comparison
There's a comparison phase between React's output, also known as Virtual DOM or VDOM, and the real DOM from the browser.
Phase 3: React Reconciliation/Commit or React Render Opt-out
If the output is the same as the DOM, React will discard the render and won't proceed further with the current change, meaning it will skip the remaining phases. See bailing out of a state update
If the output differs from the DOM, React will pass the necessary instruction to the browser to adjust its DOM. ex: add/remove DOM nodes, modify text, etc. It's during this time that
useEffect's callback is scheduled.Phase 4: React
useEffectis run and its callback is scheduledReact will run each
useEffectin the order they were registered and schedule their callbacks.===> This is the WHEN
useEffectis run, but not when it's callback is run <===It's a very important nuance you need to understand,
useEffectruns pretty early, but its callback is a deferred event that runs after the browser's render and paint for performance reasons. It's all about the renders.It's also important to understand that you can change the values in the dependency array however you want, but as long as there's no re-render, the callback given to
useEffectwill NEVER execute.Causality can be expressed like this:
useEffectis fired if React didn't bail out of the renderuseEffectwill be scheduled to run later only if at least 1 value of the dependency array has changed (think of the deferred execution as a requestIdleCallback)Phase 5: Browser Render
The browser will modify its DOM. This operation happens in memory and everything like CSS will be recalculated during this browser "render" phase
Phase 6:
useLayoutEffectReact will run synchronously the
useLayoutEffectafter the browser's DOM was modified so you can now check nodes size or position or anything that needs the DOM for processing.===> This hook runs synchronously after the browser render <===
Don't use
useLayoutEffectunless you absolutely need it as it will block the JS main thread, which would degrade your app performance.Phase 7: Browser Painting
The browser will repaint the screen and the user will see the changes.
Phase 8: React
useEffect's callbackOnce the main thread is "idling", the callbacks will be run in the order they were registered.
Again, this is NOT when
useEffectis run, it ran much earlier, it's just the callback that was scheduled that runs at this time.The callbacks run so late in the cycle because of 2 reasons:
Notes
All the phases are asynchronous, but are called in a specific order.
Since
useEffect's ===> callback <=== is deferred, it's supposed to run last but it's not a guarantee. While it can't happen before runninguseEffect(phase 4), it could happen as early as before the browser render (phase 5) depending on how the browser optimizes its work. This is why theuseLayoutEffecthook was created, to give us a handle when we need to make sure DOM modifications are applied first (although it has performance implications).Things I believe the article got wrong
I'm not aware of any change on when or how the
useEffecthas been called since its inception. I think it's more of a misconception than anything else.useEffecthas always been the same, we just didn't quite catch how it worked...Also,
useEffectis not called on mount, it's called because there was a render which happens to be the first render, aka on mount. It's a small nuance, but it's important to get the difference to better understand the model. It's not the mounting, it's the render that called it.And every subsequent calls are also because there was a render, not because the dependency array values changed. Granted, you'd need to use pretty weird patterns to not re-render and change a value, but it's possible. Try using a
refin the dependency array and change it, theuseEffectwill NEVER run after that change becauserefwill never cause a re-render. Again, it's a small nuance, but it's important to get the difference to better understand the model. It's not the change to the dependency array, it's the render that causes the call touseEffect.Same as with the previous statements. It has nothing to do with mounting, unmounting or updating values, it's all about renders:
It's all top => down, pretty simple stuff when you think about it.
And 1 more important thing to understand...
===>
useEffectwill ALWAYS be called after a render, the dependency array is only there to decide if the callback passed touseEffectshould be executed or not<===In other words:
useEffectwill execute after a render no matter what, the callback passed touseEffectwill only execute if values in the dependency array changes.Not really,
useEffectis to synchronize stuff. It shouldn't matter that your synchronization runs twice on the first render.If there's a problem, then your effect has a problem in design. It's also possible you shouldn't use
useEffectfor that particular thing either...I highly recommend to read React's official beta documentation, especially the page You might not need an effect
Great article! Why do useEffects trigger when they use callbacks sent as props from the parent? It does not happen with simple callbacks, like the ones from useState though
It’s because the useEffect does a shallow comparison of the values in its dependency array.
Consider the following scenario:
We’re comparing a function that returns
truewith another function that returnstrue. We think they’re the same, but to React, they’re two different functions because they’re both initialized as two distinct functions even though they do the same thing.If we compare references to functions, then we have equality:
Comparing references, not values achieves the effect you want.
The callbacks from useState are references.
Another way you can go about this is by wrapping your callbacks in
useCallbackfor even more control.Thanks a ton, this explains it!
Happy to serve you, Ivan!
Great article. Thank you for sharing.
The useEffect hook is truly a game-changer, it elegantly combines simplicity and functionality, making it an essential tool for any React developer..
Thank you!
Brilliant explanation @cassidoo. I love the way you write!
Thanks for this articles and the comments below help my understanding.
l value the insights and guidance you provide @cassidoo . l love the way you write.
Hi, I'm pretty sure the example with
let isCurrent = trueis incorrect, asisCurrentwill be set totrueeach time the useEffect is fired (after initial render, and wheneveruidupdates), so will mean thatsetUser()will be called at each of these times. You could make use ofuseRef()to make sure it's only called initially, or use the empty dependency array.It depends on the
fetchtiming! Iffetchis particularly slow and theuidchanges, then theisCurrentwill be false for that original fetch call. The functions inuseEffectare called in a stack of sorts, with a newisCurrentvariable for each one.I thought the
fetchpromise behaves similar to an enclosure (i.e. the value of inner vars are based on the value of outer vars at the time the function/promise is defined), so I created this codepen to check behaviour: codepen.io/ambience/pen/PodYBqaYou are correct @cassidoo, and we can see from the codepen that if multiple clicks on the button to update
uidis made in quick succession, only the last click results in thesetUsercall being made.everyone, before you use "ref flag" to bypass react 18+
strict moderunning twice, you should know it's actually designed to be a beneficial FEATURE to help catch bugs and find problems. React docs goes to great length talking about it.