engineering

What is useLayoutEffect in React?

What is useLayoutEffect in React?

In this blog, let's learn about useLayoutEffect, its use cases, code examples, interview questions, and differences between useEffect vs useLayoutEffect

Kaushal Joshi

Kaushal Joshi

Dec 13, 2024 8 min read

When working with React, you might be familiar with useEffect, the go-to hook for managing side effects. However, there's another hook that operates in a more synchronous manner: useLayoutEffect. While less commonly used, useLayoutEffect can improve user experience for scenarios requiring precise DOM manipulations.

In this blog, we'll dive deep into useLayoutEffect, understand its behavior, compare it with useEffect, and explore practical use cases. By the end, you'll know exactly when and how to use this hook to make your React applications more robust and efficient.

Prerequisites and Assumptions

This blog covers a hook that has a very niche use case. Hence, you might not use it on daily basis. This blog if for you, if...

  1. Have a basic understanding of React, functional components, and hooks.

  2. Be familiar with useEffect and its dependency array.

  3. Understand the rendering lifecycle of React components, including DOM updates and browser painting.

With that in mind, let's get started!

What Is useLayoutEffect Hook?

useLayoutEffect is a React hook that allows you to run side effects synchronously after DOM mutations but before the browser paints the screen.

This behavior positions it between React's DOM updates and the browser's rendering phase. In simple terms, it provides an opportunity to modify the DOM or measure its properties before the user sees anything on the screen.

Painting the screen refers to the process where the browser fills in the visual details (like colors, text, and images) of the webpage after calculating the layout, making the page visible to the user.

Let's understand this in detail:

  • When React updates the DOM, useLayoutEffect executes immediately before the browser repaints. This ensures that any DOM measurements or manipulations are visible in their final state to the user.

  • If you were to use useEffect in these cases, there could be a brief moment where the user sees a partially updated UI or an unintended flicker.

  • With useLayoutEffect, as updates are done before the elements are visible to the user, there's no flickering or anything as such.

The useLayoutEffect hook is like the stage crew setting everything up before the curtain rises, ensuring the stage is perfectly prepared. useEffect hook on the other hand is like the actors reviewing the performance after the show, making adjustments for future performances.

Parameters of useLayoutEffect

The useLayoutEffect hook shares its API with useEffect, making it straightforward to use.

  1. Callback Function:

    1. The callback function contains the side effect logic. Just like the useEffect hook, it optionally returns a cleanup function. React executes this function after DOM updates but before painting the DOM on the browser.

    2. On re-renders, React first runs the cleanup function (if provided) for the old render. Then, it executes the callback with the new dependencies.

    3. When a component unmounts, React runs the cleanup function for the last time.

  2. Dependency Array:

    1. Specifies dependencies for the callback. The callback runs only when these dependencies change. It could be a state, prop, or any variable or a function declared within the function.

    2. An empty array ([]) ensures the effect runs only once after the initial render.

Your first useLayoutEffect hook:

Let's write a very simple snippet that demonstrates useLayoutEffect hook:

useLayoutEffect(() => {
  console.log("This is useLayoutEffect speaking");
  return () => console.log("Cleanup up what useLayoutEffect said...");
}, []);

useEffect(() => {
  console.log("This is useEffect speaking...");
  return () => console.log("Cleaning up what useEffect said...");
}, []);

Add this within your component and check the console. And you'd understand exactly when both hooks are triggered.

Lifecycle of useLayoutEffect

Because of its synchronous behavior, it's very important to understand the React lifecycle when useLayoutEffect is used. Let's see the sequence from the start:

  1. A component starts rendering.

  2. React starts updating the DOM with updates values of the states and props.

  3. The useLayoutEffect hook is executed, blocking the process of painting UI on the screen.

  4. Once the useLayoutEffect is finished executing, the browser starts painting UI on the screen. This is when the user sees something on the screen instead of while scren.

  5. Once user sees the screen, the useEffect hook is triggered.

Here's a diagram that explains this flow:

When Should You Use useLayoutEffect?

As useLayoutEffect blocks browser from painting UI on the screen, it affects performance negatively by increasing the time required for the user to see First Contentful Paint (FCP). Hence we must use this hook only when needed. Here are some scenarios where useLayoutEffect must be considered.

Measuring DOM elements

The most important use for useLayoutEffect hook is to measure and update DOM dimensions. Suppose we are building a tooltip component. We must calculate the position of the tooltip based on the element which triggers the tooltip.

If we use the useEffect hook to calculate the position, the element will flicker and change its position once its added to the screen. With useLayoutEffect we can calculate the position beforehand, eliminating flickering completely.

Let's look at an example:

import React, { useState, useRef, useLayoutEffect } from "react";

const Tooltip = ({ text }) => {
  const [isHovering, setIsHovering] = useState(false);
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const buttonRef = useRef(null);

  useLayoutEffect(() => {
    if (isHovering && buttonRef.current) {
      const buttonRect = buttonRef.current.getBoundingClientRect();

      setPosition({
        top: buttonRect.bottom + 8, // 8px below the button

        left: buttonRect.left + buttonRect.width / 2, // Center horizontally
      });
    }
  }, [isHovering]);

  return (
    <div>
      <button
        ref={buttonRef}
        style={{ position: "relative" }}
        onMouseEnter={() => setIsHovering(true)}
        onMouseLeave={() => setIsHovering(false)}
      >
        Hover me
      </button>

      {isHovering && (
        <div
          style={{
            position: "absolute",
            top: position.top,
            left: position.left,
            transform: "translateX(-50%)", // Center horizontally
          }}
        >
          {text}
        </div>
      )}
    </div>
  );
};

export default Tooltip;

This is a tooltip component that takes text as prop and renders it on the screen when the button is hovered. As it is a tooltip text, we must render it relative to the button element, so it displays accordingly.

  1. We manage the button hovering state with useState hook.

  2. The tooltip's position is calculated using the useLayoutEffect hook. As a dependency, we passed isHovering state.

When the isHovering state updates and React re-renders the component, it will first calculate the position of the tooltip text based on the current position of the button element. The rendering process is paused until the calculation finishes.

When the calculation finishes, the browser starts painting on the screen. Now, if user hovers on the button component, the tooltip text will appear at correct position in the beginning itself.

Synchronous DOM updates

Another scenario where you'd find yourself using useLayoutEffect is when you need to update the DOM before the user sees it. For example, animations. Let's see an example of animating a Box with GSAP. It's fine if you have never used GASP, you'd still understand what this snippet does:

import React, { useRef, useLayoutEffect } from "react";

import { gsap } from "gsap";

const AnimatedBox = () => {
  const boxRef = useRef(null);

  useLayoutEffect(() => {
    const boxEl = boxRef.current;

    if (boxEl) {
      gsap.to(boxEl, { x: 300, duration: 1 });
    }
  }, []);

  return (
    <div ref={boxRef} style={{ width: 100, height: 100, background: "blue" }} />
  );
};

export default AnimatedBox;

Here, useLayoutEffect ensures the animation setup is complete before the user sees the box. Using useEffect could result in a visible "jump" before the animation starts.

useEffect vs useLayoutEffect

The useLayoutEffect hook is very similar to the useEffect hook. However, there are some crucial differences between the two.

Behavior

The useLayoutEffect hook behaves synchronously. That means, the code execution stops until the callback function is executed. Once the function executes, React moves on toward next things.

useEffect on the other hand, is asynchronous. The code execution doesn't stop if useEffect occurs. Rather, the useEffect is triggered after other code is executed.

Timing

Continuing the last point, useLayoutEffect runs before browser paints the DOM. This is because of its synchronous nature.

In contrast, useEffect runs after browser has painted the screen.

Use Cases

The useLayoutEffect is used for DOM measurements or synchronous updates. Whereas, useEffect is used for non-UI-related tasks (e.g., data fetching).

When Not to Use useLayoutEffect?

You might be wondering, if useLayoutEffect improves user experience, why not just use it every time instead of useEffect hook? But no. You should not use this hook as a replacement for useEffect hook.

Here are some scenarios where using useEffect is better than useLayoutEffect:

1. Data Fetching: Fetching data in useLayoutEffect can block the UI thread, causing performance issues.

2. Server Rendering: useLayoutEffect triggers warnings during SSR. Use useEffect instead.

3. Unnecessary Use: Avoid using useLayoutEffect unless absolutely necessary. Overuse can degrade performance.

Interview Questions

I am confident that this blog helped you completely grasp the useLayoutEffect hook. Now, let's go through some questions an interviewer might ask you during frontend interviews.

  1. When does useLayoutEffect run during the component lifecycle?

  2. What's the difference between useEffect and useLayoutEffect?

  3. When should you prefer useLayoutEffect over useEffect?

  4. How does useLayoutEffect affect performance?

  5. Why shouldn't you fetch data using useLayoutEffect?

  6. How to handle useLayoutEffect warnings on the server?

Coding Assignment

By now, you have a full understanding of what useLayoutEffect is, how it works, when to use it, and most importantly, when not to use it. To finish off, write a component that implements what we learned today.

Create a React component that displays a box. Inside the box, render another smaller box centered within it. Use useLayoutEffect to ensure the smaller box is perfectly centered after rendering.

Wrapping Up

I hope you found this blog helpful. If you manage to complete the coding assignment, I'd be very happy to know. I am most active on Twitter and Peerlist, if you want to say hi!

If you are looking for frontend developer jobs, or want to share a cool project you built, check out Peerlist!

Create Profile

or continue with email

By clicking "Create Profile“ you agree to our Code of Conduct, Terms of Service and Privacy Policy.