There are two React hooks, useEffect
and useLayoutEffect
, that appear to work pretty much the same.
The way you call them even looks the same.
useEffect(() => {
// do side effects
return () => /* cleanup */
}, [dependency, array]);
useLayoutEffect(() => {
// do side effects
return () => /* cleanup */
}, [dependency, array]);
But they’re not quite the same. Read on for what makes them different and when to use each. (tl;dr: most of the time you want plain old useEffect
)
The Difference Between useEffect and useLayoutEffect
It’s all in the timing.
useEffect
runs asynchronously and after a render is painted to the screen.
So that looks like:
- You cause a render somehow (change state, or the parent re-renders)
- React renders your component (calls it)
- The screen is visually updated
- THEN
useEffect
runs
useLayoutEffect
, on the other hand, runs synchronously after a render but before the screen is updated. That goes:
- You cause a render somehow (change state, or the parent re-renders)
- React renders your component (calls it)
useLayoutEffect
runs, and React waits for it to finish.- The screen is visually updated
99% of the time, useEffect
Most of the time your effect will be synchronizing some bit of state or props with something that doesn’t need to happen IMMEDIATELY or that doesn’t affect the page visually.
Like if you’re fetching data, that’s not going to result in an immediate change.
Or if you’re setting up an event handler.
Or if you’re resetting some state when a modal dialog appears or disappears.
Most of the time, useEffect
is the way to go.
When to useLayoutEffect
The right time to useLayoutEffect
instead? You’ll know it when you see it. Literally ;)
If your component is flickering when state is updated – as in, it renders in a partially-ready state first and then immediately re-renders in its final state – that’s a good clue that it’s time to swap in useLayoutEffect
.
This’ll be the case when your update is a 2-step (or multi-step) process. Do you want to “batch” a couple updates together before redrawing the screen? Try useLayoutEffect
.
I think of useLayoutEffect
as the way to squeeze in a little extra work before React updates the DOM. “Hey, you’re making some changes already – could you throw this one in there too? Awesome.”
Here’s a (contrived) example so you can see what I mean.
When you click the page*, the state changes immediately (value
resets to 0), which re-renders the component, and then the effect runs – which sets the value to some random number, and re-renders again.
The result is that two renders happen in quick succession.
import React, {
useState,
useLayoutEffect
} from 'react';
import ReactDOM from 'react-dom';
const BlinkyRender = () => {
const [value, setValue] = useState(0);
useLayoutEffect(() => {
if (value === 0) {
setValue(10 + Math.random() * 200);
}
}, [value]);
console.log('render', value);
return (
<div onClick={() => setValue(0)}>
value: {value}
</div>
);
};
ReactDOM.render(
<BlinkyRender />,
document.querySelector('#root')
);
* In general, putting onClick
handlers on div
s is bad for accessibility (use buttons instead!), but this is a throwaway demo. Just wanted to mention it!
Try the useLayoutEffect version and then try the version with useEffect.
Notice how the version with useLayoutEffect
only updates visually once even though the component rendered twice. The useEffect
version, on the other hand, visually renders twice, so you see a flicker where the value is briefly 0
.
Should I useEffect or useLayoutEffect?
Most of the time, useEffect
is the right choice. If your code is causing flickering, switch to useLayoutEffect
and see if that helps.
Because useLayoutEffect
is synchronous a.k.a. blocking a.k.a. the app won’t visually update until your effect finishes running… it could cause performance issues like stuttering if you have slow code in your effect. Coupled with the fact that most effects don’t need the world to pause while they run, regular useEffect
is almost always the one to use.