What is useLayoutEffect Hook in React?
This article provides a comprehensive guide to the
useLayoutEffect hook in React, explaining its purpose, how
it functions, and how it differs from the standard
useEffect hook. You will learn the specific scenarios where
useLayoutEffect is necessary, such as measuring DOM
elements to prevent visual flickering, along with a practical code
example and performance considerations.
Understanding useLayoutEffect
The useLayoutEffect hook is a companion to
useEffect in React. The primary difference between the two
lies in the timing of their execution. While useEffect runs
asynchronously after the browser has painted the updated screen,
useLayoutEffect fires synchronously before the
browser has a chance to paint those updates to the screen.
Because it runs synchronously, any state updates made inside
useLayoutEffect will be processed before the browser
redraws. This prevents the user from seeing a temporary, outdated visual
state, making it ideal for manipulating or measuring the DOM.
The Difference: useEffect vs. useLayoutEffect
To understand when to use useLayoutEffect, it is helpful
to look at the React rendering lifecycle pipeline:
- Render: React renders the components and calculates the Virtual DOM.
- Commit: React mutates the actual DOM.
- useLayoutEffect: React runs
useLayoutEffectsynchronously. - Paint: The browser paints the changes onto the screen.
- useEffect: React runs
useEffectasynchronously.
Because useEffect runs after the paint, if you
modify the DOM or update state that affects the layout inside it, the
user might see a brief flash or flicker. The element renders once in its
initial state, and then immediately jumps to its new state.
useLayoutEffect eliminates this flicker by running before
the paint occurs.
When to Use useLayoutEffect
You should only use useLayoutEffect when your code
directly interacts with the DOM and affects the visual layout of an
element. Common use cases include:
- Measuring DOM elements: Getting the height, width, scroll position, or bounding rectangle of a DOM node before positioning another element.
- Repositioning elements: Aligning tooltips, popovers, or dropdown menus relative to a trigger button.
- Animating layout transitions: Triggering smooth animations that must start precisely from a calculated DOM state.
For almost all other side effects—such as fetching data, setting up
event listeners, or updating state that does not cause layout shifts—you
should stick to useEffect to avoid blocking browser
painting.
Code Example
Here is a practical example of using useLayoutEffect to
measure the height of a DOM element before rendering content that
depends on that measurement:
import React, { useState, useLayoutEffect, useRef } from 'react';
function Tooltip() {
const [boxHeight, setBoxHeight] = useState(0);
const elementRef = useRef(null);
useLayoutEffect(() => {
if (elementRef.current) {
// Measure the actual height of the DOM element
const height = elementRef.current.getBoundingClientRect().height;
setBoxHeight(height);
}
}, []); // Empty dependency array runs once on mount
return (
<div>
<div ref={elementRef} style={{ padding: '20px', background: 'lightgray' }}>
Dynamic Content Area
</div>
<p>The height of the box above is: {boxHeight}px</p>
</div>
);
}
export default Tooltip;In this example, React measures the element’s height and updates the state synchronously. The user sees the final, corrected layout immediately upon initial render, without any transitional jump or layout shift.
Performance Warning
Because useLayoutEffect is synchronous, it blocks the
browser from painting the screen until the hook finishes executing. If
you perform heavy computations or cause chain-reaction state updates
inside this hook, it can significantly degrade your application’s
performance and make the UI feel sluggish. Always default to
useEffect unless you explicitly need to prevent visual
glitching.