Confused over useEffect
hook and useLayoutEffect
hooks?
In this article, we will look into
how useEffect
and useLayoutEffect
differ from each other
and their use-cases.
Before we dive into the difference between the two it is worth mentioning that both the hooks -
- Handle side-effects in React
- Have an identical signature
Let’s consider a simple example of a counter.
When the component is mounted
Count: {count}
appears on the screen.
When the button is clicked
the counter is incremented.
The same behavior is seen
when useLayoutEffect
is replaced with useEffect
.
How is useEffect
different from useLayoutEffect
?
The difference between them is the timing of their invocation.
This diagram will help to understand the flow better.
Image Credits : hook-flow
useEffect
runs asynchronously after a render is painted to the screen,
unblocking the browser paint process.
So that looks like:
- We cause a render somehow (through the state, or the parent re-renders or context change). In our case by clicking on the Increment button.
- React updates the state internally. Counter value gets updated to 1.
- React handles the DOM mutation.
<h1>Count: 0 </h1>
gets changed to<h1>Count: 1 </h1>
- The browser paints this DOM change to the browser’s screen.
In our case
Count: 1
is painted on the screen. - The
useEffect
function is fired after the browser has painted the DOM changes. PrintsuseEffect is fired
.
However, useLayoutEffect
fires synchronously after all DOM mutations.
So that looks like:
- We cause a render somehow (through the state, or the parent re-renders or context change). In our case by clicking on the Increment button.
- React updates the state internally. Counter value gets updated to 1.
- React handles the DOM mutation.
<h1>Count: 0 </h1>
gets changed to<h1>Count: 1 </h1>
useLayoutEffect
is fired immediately after the DOM mutations.- The browser paints this DOM change to the browser’s screen.
In our case
Count: 1
is painted on the screen.
In simple words, useLayoutEffect doesn’t care whether the browser has painted the DOM changes or not. It triggers the function right after the DOM mutations are computed.
Use-cases for useEffect
and useLayoutEffect
Deciding which hook to choose between useEffect and useLayoutEffect may be tricky.
Understanding the situation is the key!
99% of the time useEffect
is what we want to use.
Most of the time we are fetching data and setting up event handlers
that do not need to happen immediately.
It also does not affect page appearance.
For all such cases, we should use the useEffect
hook.
So what is the right time to use useLayoutEffect
?
If our effect will mutate the DOM (like getting the scroll position or other styles for an element) or involves animation prefer useLayoutEffect over useEffect.
Reason:
useEffect hook is called after the screen is painted. Therefore mutating the DOM again immediately after the screen has been painted, will cause a flickering effect if the mutation is visible to the client.
Let’s take the example of a tooltip.
Here we are taking measurements of the button
from DOM
and
manipulating the position of the tooltip.
We see a flicker for a small duration after the button is clicked. This is because the useEffect is fired asynchronously.
Replacing useEffect with useLayoutEffect removes the flicker.
useEffect in React 18
Running the same useEffect code with React 18 does not cause flickering.
Yes, this is true!
In React 18, useEffect fires synchronously when it’s the result of a discrete input.
Now, what is discrete input?
A discrete input is a type of event where the result of one event can affect the behavior of the next, like clicks.
In our case, clicking on the button, fires useEffect synchronously avoiding flickering.
To read about the changes in useEffect in React 18 check-out this link.
Wrap up
- Replacing the useEffect hook with useLayoutEffect may not have a noticeable effect in simple apps, it is strongly advised not to do so.
- useLayoutEffect can be expensive to run, so it should be used only when required.
- In React 18, only for discrete events useEffect will fire synchronously. For most of the cases(non-discrete events), React will defer the effects until after paint.
Refer to this link to know about useLayoutEffect and server-side rendering.