Editor’s note: This post was last updated on 22 December 2021.
Assuming you really understand the difference between useEffect
and useLayoutEffect
, can you explain it in simple terms? Or can you describe their nuances with concrete, practical examples?
In this tutorial, I’ll describe the differences between useEffect
and useLayoutEffect
with concrete examples that’ll help you cement your understanding. Let’s get started!
Table of contents
- What’s the difference between
useEffect
anduseLayoutEffect?
- Firing:
useEffect
vs.useLayoutEffect
- useEffect and
useLayoutEffect
examples - 1. Time of execution
- 2. Performance
- 3. Inconsistent visual changes
- Conclusion
What’s the difference between useEffect
and useLayoutEffect
?
Sprinkled all over the official Hooks API Reference are pointers to the differences between useEffect
and useLayoutEffect
. Perhaps the most prominent of these is found in the first paragraph detailing the useLayoutEffect
Hook, reading “the signature is identical to useEffect
, but it fires synchronously after all DOM mutations”.
The first clause in the sentence above is easy enough to understand. The signatures for useEffect
and useLayoutEffect
are exactly the same, respectively:
useEffect(() => { // do something }, [array, dependency]) useLayoutEffect(() => { // do something }, [array, dependency])2
If you were to go through a codebase and replace every useEffect
call with useLayoutEffect
, it would work in most cases. For instance, I’ve taken an example from the React Hooks Cheatsheet that fetches data from a remote server and changes the implementation to use useLayoutEffect
over useEffect
:
In our example, it will still work. Now, we’ve established the first important fact, useEffect
and useLayoutEffect
have the same signature. This trait makes it easy to assume that these two Hooks behave in the same way. However, the second part of the aforementioned quote above feels a little more fuzzy to most people, stating “it fires synchronously after all DOM mutations”. Essentially, the difference between useEffect
and useLayoutEffect
lies solely in when the two are fired.
Firing: useEffect
vs. useLayoutEffect
Let’s consider the following contrived counter application:
function Counter() { const [count, setCount] = useState(0) useEffect(() => { // perform side effect sendCountToServer(count) }, [count]) <div> <h1> {`The current count is ${count}`} </h1> <button onClick={() => setCount(count => count + 1)}> Update Count </button> </div> } // render Counter <Counter />
When the component is mounted, the following code is painted to the user’s browser:
// The current count is 0
With every click of the button, the counter state is updated, the DOM mutation is printed to the screen, and the effect function is triggered. Here’s what’s really happening:
Step 1. The user performs an action, i.e., clicking the button
Step 2. React updates the count state
variable internally
Step 3. React handles the DOM mutation
With the click comes a state update, which in turn triggers a DOM mutation. The text content of the h1
element has to be changed from the current count is previous value
to the current count is new value
.
Step 4. The browser then paints this DOM change to the browser’s screen
Steps 1, 2, and 3 do not show any visual changes to the user. Only after the browser has painted the changes or mutations to the DOM does the user actually see a change. React hands over the details about the DOM mutation to the browser engine, which figures out the entire process of painting the change to the screen.
Step 5. Only after the browser has painted the DOM change(s) is the useEffect
function fired
Here’s an illustration to help you remember the entire process.
Note that the function passed to useEffect
will be fired only after the DOM changes are painted to the screen. The official docs put it this way, “the function passed to useEffect
will run after the render is committed to the screen”.
Technically speaking, the effect function is fired asynchronously to not block the browser paint process. The illustration above does not make obvious that the operation is incredibly fast for most DOM mutations. If the useEffect
function itself triggers another DOM mutation, this would happen after the first, but the process is usually pretty fast.
Note: Although
useEffect
is deferred until after the browser has painted, it’s guaranteed to fire before any new renders. React will always flush a previous render’s effects before starting a new update.
Now, how does this differ from the useLayoutEffect
Hook? Unlike useEffect
, the function passed to the useLayoutEffect
Hook is fired synchronously after all DOM mutations.
In simplified terms, useLayoutEffect
doesn’t really care whether the browser has painted the DOM changes or not. It triggers the function right after the DOM mutations are computed.
While this seems unideal, it is highly encouraged in specific use cases. For example, a DOM mutation that must be visible to the user should be fired synchronously before the next paint, preventing the user from receiving a visual inconsistency. We’ll see an example of this later in the article.
Remember, updates scheduled inside useLayoutEffect
will be flushed synchronously before the browser has a chance to paint.
useEffect
and useLayoutEffect
examples
The main difference between useEffect
and useLayoutEffect
lies in when they are fired, but regardless, it’s hard to tangibly quantify this difference without looking at concrete examples. In this section, I’ll highlight three examples that amplify the significance of the differences between useEffect
and useLayoutEffect
.
1. Time of execution
Modern browsers are very fast. We’ll employ some creativity to see how the time of execution differs between useEffect
and useLayoutEffect
. In the first example, we’ll consider a counter similar to the one we looked at earlier:
In this counter, we have the addition of two useEffect
calls:
useEffect(() => { console.log("USE EFFECT FUNCTION TRIGGERED"); }); useEffect(() => { console.log("USE SECOND EFFECT FUNCTION TRIGGERED"); });
Note that the effects log different texts depending on which is triggered, and as expected, the first effect function is triggered before the second.
When there is more than one useEffect
call within a component, the order of the effect calls is maintained. The first is triggered, then the second, and the sequence continues. Now, what happens if the second useEffect
Hook is replaced with a useLayoutEffect
Hook?
useEffect(() => { console.log("USE EFFECT FUNCTION TRIGGERED"); }); useLayoutEffect(() => { console.log("USE LAYOUT EFFECT FUNCTION TRIGGERED"); });
Even though the useLayoutEffect
Hook is placed after the useEffect
Hook, the useLayoutEffect
Hook is triggered first!
The useLayoutEffect
function is triggered synchronously before the DOM mutations are painted. However, the useEffect
function is called after the DOM mutations are painted.
In the next example, we’ll look at plotting graphs with respect to the time of execution for both the useEffect
and useLayoutEffect
Hooks. The example app has a button that toggles the visual state of a title, whether shaking or not. Here’s the app in action:
I chose this example to make sure the browser actually has some changes to paint when the button is clicked, hence the animation. The visual state of the title is toggled within a useEffect
function call. If it interests you, you can view the implementation.
I gathered significant data by toggling the visual state every second, meaning I clicked the button using both the useEffect
and useLayoutEffect
Hooks.
Using performance.now
, I measured the difference between when the button was clicked and when the effect function was triggered for both useEffect
and useLayoutEffect
. I gathered the following data:
From this data, I created a chart to visually represent the time of execution for useEffect
and useLayoutEffect
:
Essentially, the graph above represents the time difference between when the useEffect
and useLayoutEffect
effect functions are triggered, which in some cases is of a magnitude greater than 10x. See how much later useEffect
is fired when compared to useLayoutEffect
?
You’ll see how this time difference plays a huge role in use cases like animating the DOM, explained in example three below.
2. Performance
Expensive calculations are, well, expensive. If handled poorly, these can negatively impact the performance of your application. With applications that run in the browser, you have to be careful not to block the user from seeing visual updates just because you’re running a heavy computation in the background.
The behavior of both useEffect
and useLayoutEffect
differ in how heavy computations are handled. As stated earlier, useEffect
will defer the execution of the effect function until after the DOM mutations are painted, making it the obvious choice out of the two.
As an aside, I know
useMemo
is great for memoizing heavy computations. This article neglects that fact, instead comparinguseEffect
anduseLayoutEffect
.
As an example, I’ve set up an app that’s not practical, but decent enough to work for our use case. The app renders with an initial screen that seems harmless:
However, it has two clickable buttons that trigger some interesting changes. For example, clicking the 200 bars button sets the count state to 200:
It also forces the browser to paint 200 new bars to the screen:
This is not a very performant way to render 200 bars, as I’m creating new arrays every single time, but the point of our example is to make the browser work:
... return ( ... <section style={{ display: "column", columnCount: "5", marginTop: "10px" }}> {new Array(count).fill(count).map(c => ( <div style={{ height: "20px", background: "red", margin: "5px" }}> {c} </div> ))} </section> )
The click also triggers a heavy computation:
... useEffect(() => { // do nothing when count is zero if (!count) { return; } // perform computation when count is updated. console.log("=== EFFECT STARTED === "); new Array(count).fill(1).forEach(val => console.log(val)); console.log(`=== EFFECT COMPLETED === ${count}`); }, [count]);
Within the UseEffect
function, I create a new array with a length totaling the count number, in this case, an array of 200 values. I loop over the array and print something to the console for each value in the array.
We’ll still need to pay attention to the screen update and our log consoles to see how this behaves. For useEffect
, our screen is updated with the new count value before the logs are triggered:
Here’s the same screencast in slow-motion. There’s no way you’ll miss the screen update happening before the heavy computation! So, is this behavior the same with useLayoutEffect
? No! Far from it.
With useLayoutEffect
, the computation will be triggered before the browser has painted the update. The computation takes some time, eating into the browser’s paint time. Check out the same action from above replaced with useLayoutEffect
:
Again, you can watch it in slow motion. You can see how useLayoutEffect
stops the browser from painting the DOM changes for a bit. You can play around with the demo, but be careful not to crash your browser.
Why does this difference in how heavy computations are handled matter? When possible, you should choose the useEffect
Hook for cases where you want to be unobtrusive in the dealings of the browser paint process. In the real world, this is most of the time, except for when you’re reading layout from the DOM or doing something DOM-related that needs to be painted ASAP. In the next section, we’ll see an example in action.
3. Inconsistent visual changes
useLayoutEffect
truly shines when handling inconsistent visual changes. As an example, let’s consider these real scenarios I encountered myself in while working on my Udemy video course on Advanced Patterns with React Hooks.
With useEffect
, you get a flicker before the DOM changes are painted, which was related to how refs are passed on to custom Hooks. Initially, these refs start off as null
before actually being set when the attached DOM node is rendered.
However, consider the following screencasts. With useEffect
:
With useLayoutEffect
:
If you rely on these refs to perform an animation as soon as the component mounts, then you’ll find an unpleasant flickering of browser paints happen before your animation kicks in. This is the case with useEffect
, but not useLayoutEffect
.
Even without this flicker, sometimes you may find useLayoutEffect
produces animations that look buttery, cleaner and faster than useEffect
. Be sure to test both Hooks when working with complex user interface animations.
Conclusion
In this article, we reviewed the useEffect
and useLayoutEffect
Hooks in React, looking into the inner workings and best use cases for each. We went over examples of both Hooks regarding their firing, performance, and visual changes.
Cut through the noise of traditional React error reporting with LogRocket
LogRocket is a React analytics solution that shields you from the hundreds of false-positive errors alerts to just a few truly important items. LogRocket tells you the most impactful bugs and UX issues actually impacting users in your React applications.

Focus on the React bugs that matter — try LogRocket today.
OK, IMHO, this whole article was kinda of overkill. But, kudos for the author anyway.
TL;DR;
“`
useLayoutEffect: If you need to mutate the DOM and/or DO need to perform measurements
useEffect: If you don’t need to interact with the DOM at all or your DOM changes are unobservable (seriously, most of the time you should use this).
“`
by: Kent C. Dodd (https://kentcdodds.com/blog/useeffect-vs-uselayouteffect)
Simple and Clear !!
that was supper helpful thx 🙂
This was a super helpful article. I liked the way author was showing with live examples the differences between useEffect and useLayoutEffect hooks. Now I finally got it. Awesome!
Utilising live recordings and different examples is quite helpful for grasping the concept to the core. Thanks for the article.
Thank you so much for this! I was searching for something does the work of useLayoutEffect() to prevent the dimming of loading animation while execution of multiple useEffects.
In my code, my first useEffect calls an Api and that data was using in next api written in next useEffect, also first useEffect was printing on the browser after that fetches the data of first API. Then only it was calling the other useEffect(). That was making the dim on loading. By using useLayoutEffect () the job is done asynchronously.
Very helpful🙂