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:
useEffect(Asynchronous): Runs after the React render phase and after the browser has painted the updated screen. This is the default choice for most side effects (e.g., fetching data, setting up event listeners) because it does not block the browser paint process.useLayoutEffect(Synchronous): Runs after the React render phase but before the browser paints. If you make a state change insideuseLayoutEffect, the screen will not update twice; React will perform the updates synchronously and display only the final state to the user.
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 mountsBecause 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:
- Measuring DOM Nodes: When you need to read an
element’s width, height, or position
(
getBoundingClientRect(),offsetWidth,scrollHeight) and use those values to reposition elements. - Preventing Flickering: When rendering changes
visually based on DOM state, and using
useEffectcauses a noticeable jump or visual glitch. - Synchronizing Scroll Positions: When you need to manually force a scroll container to a specific position before the user sees the initial render.