React: useLayoutEffect vs useEffect
For 99% of your components, you should use useEffect. However, there is a very specific class of visual bugs—usually manifesting as a quick "flicker" on the screen when a component mounts—that can only be solved using useLayoutEffect.
What is the exact difference between
useEffectanduseLayoutEffect, and when should you use the latter?
To answer this, you must understand the Browser Rendering Pipeline.
1. The Rendering Pipeline
When React updates a component's state, the following sequence occurs:
- Render Phase: React calls your component function and calculates the new Virtual DOM.
- Commit Phase: React mutates the physical DOM to match the new Virtual DOM.
- Browser Paint: The browser physically paints the new DOM nodes onto the user's screen.
Where do the hooks fit into this timeline?
useEffect (Asynchronous / Non-Blocking)
useEffect fires AFTER the Browser Paint (Step 3).
Because it fires after the browser has already drawn the screen, it does not block the UI. This makes it highly performant for operations like fetching data, setting up subscriptions, or logging analytics.
useLayoutEffect (Synchronous / Blocking)
useLayoutEffect fires BEFORE the Browser Paint, immediately after the physical DOM is mutated (Between Step 2 and Step 3).
Because it fires synchronously before the screen is painted, it blocks the browser from drawing. The browser will wait for your useLayoutEffect to finish executing before it shows anything to the user.
2. The Problem: UI Flickering
Imagine you are rendering a tooltip. You need to measure the width of a button in the DOM to know exactly where to position the tooltip.
If you use useEffect:
- React renders the tooltip at its default position (e.g.,
top: 0). - The browser paints the screen. (The user briefly sees the tooltip at the top of the screen).
useEffectruns, measures the button, and updates the tooltip state totop: 50px.- React re-renders.
- The browser paints again. (The tooltip teleports to the correct position).
This causes a highly noticeable, ugly visual flicker.
3. The Solution: useLayoutEffect
By switching to useLayoutEffect, you intercept the rendering pipeline before the user sees anything.
import { useState, useRef, useLayoutEffect } from 'react';
function Tooltip({ children }) {
const [topPos, setTopPos] = useState(0);
const tooltipRef = useRef(null);
// Fires BEFORE the browser paints to the screen!
useLayoutEffect(() => {
// 1. Measure the physical DOM node
const rect = tooltipRef.current.getBoundingClientRect();
// 2. Trigger a synchronous re-render with the new position
if (rect.height > 100) {
setTopPos(50);
}
}, []);
return (
<div ref={tooltipRef} style={{ top: `${topPos}px`, position: 'absolute' }}>
{children}
</div>
);
}Because useLayoutEffect updates the state before the paint, React throws away the initial render and immediately calculates the new render. The browser only paints once, skipping the flicker entirely.
Senior-Level Interview Answer
Both
useEffectanduseLayoutEffectexecute after React has committed mutations to the physical DOM, but they differ fundamentally in their timing relative to the browser's painting pipeline.useEffectis asynchronous and non-blocking; it executes after the browser has painted the screen, making it the performant choice for standard side effects like data fetching.useLayoutEffectis synchronous and blocking; it executes before the browser paints. If a developer usesuseLayoutEffectto read DOM layout properties (likegetBoundingClientRect()) and subsequently triggers a state update, React will synchronously re-render the component and merge the updates before allowing the browser to paint. This intercepts and prevents visual layout thrashing or flickering, which is crucial for dynamic UI elements like tooltips or masonry grids.
Common Interview Mistakes
❌ Using useLayoutEffect everywhere "just in case"
Because useLayoutEffect is synchronous, it blocks the main thread. If you use it for heavy computations or network requests, the entire browser window will freeze, unable to paint the next frame until the effect completes. It should only be used when mutating the DOM in a way that would cause visual jitter.
