Ohans Emmanuel Author of Understanding Redux. I love God. I love GF a little too much 💕🤣 Read The Redux.js Books.

React useLayoutEffect vs. useEffect with examples

7 min read 2055

Useeffect Vs Uselayouteffect Examples

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 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:

React Hooks Cheatsheet Example

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:

We made a custom demo for .
No really. Click here to check it out.

// 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.

Dom Mutation Printed Screen

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.

React Dom Browser Painting Example

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”.

Useeffect Function Illustration

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.

useLayoutEffect Synchronous Fire

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:

React Hooks Counter Example UI

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.

Counter Example First Function Triggered

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!

useEffect useLayoutEffect Sequencing Example

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:

useEffect useLayoutEffect Title Shaking

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:

Data Collection Time Evaluation

From this data, I created a chart to visually represent the time of execution for useEffect and useLayoutEffect:

Time Of Evaluation Graph

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 comparing useEffect and useLayoutEffect.

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:

useEffect useLayoutEffect Performance Render

However, it has two clickable buttons that trigger some interesting changes. For example, clicking the 200 bars button sets the count state to 200:

200 Bars Button Output Example

It also forces the browser to paint 200 new bars to the screen:

App Render 200 Bars

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:

useEffect Logs 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:

useLayoutEffect Computation Triggered Example

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:

useEffect Screencast

With useLayoutEffect:

useLayoutEffect Screencast

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.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Ohans Emmanuel Author of Understanding Redux. I love God. I love GF a little too much 💕🤣 Read The Redux.js Books.

4 Replies to “React useLayoutEffect vs. useEffect with examples”

  1. 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)

  2. 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!

Leave a Reply