useEffect vs useLayoutEffect in React
Both useEffect and useLayoutEffect are React hooks for running side effects, but they differ in when they run in React’s rendering lifecycle.
The Core Difference is:
useEffectruns after the browser has painted the screen, whileuseLayoutEffectruns before the browser has painted the screen.
This timing difference is the source of all their other distinctions.
useEffect
useEffect runs asynchronously after the browser has painted the screen.
Use cases: most side effects, data fetching, setting up subscriptions and event listeners.
Since it does not block the main thread, it is more performant.
Example:
Here, React paints the screen with the new count first, then updates the document title.
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
useLayoutEffect
useLayoutEffect runs synchronously after DOM mutations but before the browser paints.
useLayoutEffect is best for reading layout from the DOM and synchronously re-rendering. This is useful when you need to measure a DOM element (like its size or scroll position) and then change something based on that measurement.
useLayoutEffect prevents a visual “flicker” where the user first sees the initial render and then the updated state immediately after. For example, calculating a tooltip’s position and then setting its top and left styles.
Example:
Here, React updates the DOM, then immediately runs this hook before the user sees anything.
useLayoutEffect(() => {
const el = ref.current;
const { height } = el.getBoundingClientRect();
console.log("Height:", height);
}, []);
Practical Example: Preventing Flicker
Let’s say you have a tooltip component that needs to be positioned correctly. You can use useLayoutEffect to measure the tooltip’s size and position it correctly before the browser paints.
function Tooltip() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const tooltipRef = useRef(null);
// ❌ useEffect - might cause flicker
useEffect(() => {
const rect = tooltipRef.current.getBoundingClientRect();
setPosition({ x: rect.width, y: rect.height });
}, []);
// ✅ useLayoutEffect - no flicker
useLayoutEffect(() => {
const rect = tooltipRef.current.getBoundingClientRect();
setPosition({ x: rect.width, y: rect.height });
}, []);
return (
<div ref={tooltipRef} style={{ left: position.x, top: position.y }}>
Tooltip
</div>
);
}
Summary
useEffect: async, runs after paint (doesn’t block the UI).
useLayoutEffect: sync, runs before paint (can block UI if heavy).
Use useEffect in most of the cases. Only switch to useLayoutEffect if you notice visual problems where you need to measure DOM elements or update styles/layout before the user sees it.