How to Use useLayoutEffect in React

This article provides a straightforward guide on how to implement the useLayoutEffect hook in React. You will learn what this hook is, how it differs from the standard useEffect hook, when you should use it to prevent visual flickers, and how to write the code to measure and manipulate DOM elements before the browser paints the screen.

What is useLayoutEffect?

The useLayoutEffect hook is a version of useEffect that fires synchronously after all DOM mutations but before the browser has a chance to paint those changes to the screen. This makes it ideal for reading layout information from the DOM (such as element dimensions or scroll positions) and immediately making synchronous changes to the DOM to prevent visual glitches.

The Difference Between useEffect and useLayoutEffect

While the syntax for both hooks is identical, they execute at different times in the React rendering lifecycle:

Syntax of useLayoutEffect

The syntax of useLayoutEffect is identical to useEffect. It accepts a callback function and an optional dependency array:

import { useLayoutEffect } from 'react';

useLayoutEffect(() => {
  // Your synchronous DOM operations or measurements here
  
  return () => {
    // Optional cleanup function
  };
}, [dependencies]);

How to Implement useLayoutEffect (Step-by-Step)

A classic use case for useLayoutEffect is positioning an element (like a tooltip or popup) based on the size of another element. If you use useEffect for this, the element might briefly render in the wrong position before jumping to the correct position, causing a visible flicker.

Here is how to implement useLayoutEffect to measure a DOM node and adjust layout synchronously:

1. Set Up the Component and Refs

First, import the necessary hooks and create a useRef to target the DOM element you want to measure.

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

function TooltipButton() {
  const [tooltipHeight, setTooltipHeight] = useState(0);
  const buttonRef = useRef(null);
  const tooltipRef = useRef(null);

  return (
    <div style={{ position: 'relative', padding: '50px' }}>
      <button ref={buttonRef}>Hover over me</button>
      <div 
        ref={tooltipRef} 
        style={{ 
          position: 'absolute', 
          top: `${-tooltipHeight}px`, // Position based on measured height
          background: 'black', 
          color: 'white',
          padding: '8px'
        }}
      >
        This is a dynamic tooltip!
      </div>
    </div>
  );
}

2. Implement the useLayoutEffect Hook

Now, implement useLayoutEffect to measure the actual height of the tooltip DOM element before the browser draws it on the screen.

useLayoutEffect(() => {
  if (tooltipRef.current) {
    // Measure the actual height of the DOM element
    const { height } = tooltipRef.current.getBoundingClientRect();
    
    // Update state synchronously
    setTooltipHeight(height);
  }
}, []); // Empty array ensures this only runs once after the component mounts

Because useLayoutEffect runs synchronously before the browser paints, the user will never see the tooltip render at the default top position before moving to the correct calculated height.

When to Use useLayoutEffect

To keep your application’s performance optimal, you should default to useEffect and only use useLayoutEffect in specific scenarios: