When would you reach for useLayoutEffect instead of useEffect, and what's the cost of getting it wrong?
Understanding the browser paint cycle and where each effect fires relative to it.
Both run after render, but at different points relative to paint. useEffect fires asynchronously *after* the browser has painted — so if your effect mutates the DOM in a way the user can see (measuring an element then repositioning a tooltip, syncing scroll position), the user briefly sees the un-adjusted frame, i.e. a flicker. useLayoutEffect fires synchronously *after DOM mutations but before paint*, so you can read layout (getBoundingClientRect, offsetHeight) and write derived styles in the same frame, and the user never sees the intermediate state. The cost: useLayoutEffect blocks painting, so heavy work there freezes the UI — use it only for reads/writes that must happen before paint, and keep it cheap. Default to useEffect; escalate to useLayoutEffect only to kill a visible flicker caused by measuring-then-mutating layout. Note it also warns during SSR because there's no layout phase on the server — guard with a client check or use the useEffect fallback for isomorphic components.
You measure the DOM then mutate it and see a flicker → useLayoutEffect (before paint). Otherwise useEffect.
function Tooltip({ targetRef }) {
const tipRef = React.useRef(null);
const [pos, setPos] = React.useState({ top: 0, left: 0 });
// Runs before paint: measure target, position tooltip in the same frame.
React.useLayoutEffect(() => {
const t = targetRef.current.getBoundingClientRect();
const h = tipRef.current.offsetHeight;
setPos({ top: t.top - h, left: t.left }); // no visible flicker
}, [targetRef]);
return <div ref={tipRef} style={{ position: 'fixed', ...pos }}>Hint</div>;
}