useLayoutEffect When to Use
useLayoutEffect runs before paint: measure and mutate DOM without flicker, SSR warnings, and why useEffect is usually enough.
- react
- uselayouteffect-when
useLayoutEffect has the same signature as useEffect, but it fires synchronously after DOM mutations and before the browser paints. Use it when you must measure layout or adjust the DOM before the user sees a frame — otherwise prefer useEffect.
Docs: useLayoutEffect, useEffect.
Timeline
- Render (compute UI).
- Commit DOM updates.
useLayoutEffectsetup (still before paint).- Browser paint.
useEffectsetup (after paint).
Blocking the paint path means heavy work in layout effects janks the UI. Keep them tiny.
Canonical use: measure then position
function Tooltip({ targetRef, children }) {
const tipRef = useRef(null);
const [offset, setOffset] = useState({ top: 0, left: 0 });
useLayoutEffect(() => {
const target = targetRef.current;
const tip = tipRef.current;
if (!target || !tip) return;
const rect = target.getBoundingClientRect();
setOffset({
top: rect.bottom + window.scrollY + 8,
left: rect.left + window.scrollX,
});
}, [targetRef, children]);
return createPortal(
<div ref={tipRef} style={{ position: 'absolute', ...offset }}>
{children}
</div>,
document.body
);
}
With useEffect, users may flash the tooltip at (0,0) for one frame. Layout effect removes that flicker.
SSR warning
useLayoutEffect warns on the server because there is no layout to read. Patterns:
- Use
useEffectwhen flicker is acceptable. - Render a placeholder on server/first client paint, then measure.
- Branch with a small wrapper that uses layout effect only after mount.
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
Know that the isomorphic fallback still cannot measure on the server — it only silences the warning.
When not to use it
| Temptation | Prefer |
|---|---|
| Data fetch | useEffect, router loaders, RSC |
| Subscribing to window | useEffect (paint delay OK) |
| Syncing props to state | Usually neither — derive or key remount |
| Long JS work | Move off critical path; do not block paint |
Interview out-loud
“useLayoutEffect runs after DOM updates but before paint, so I use it to measure and adjust layout without flicker. useEffect runs after paint and is the default for subscriptions and fetches. Heavy layout effects cause jank, and they need care under SSR.”
Related on this site
Further reading
Production checklist
- Source of truth clear for every piece of UI state?
- Remount, route change, and Strict Mode cleanup paths handled?
- Urgent updates separated from deferrable work?
- Profiled before memo, virtualization, or context splits?
- Keyboard, focus, and accessible names still work after the change?
Edge cases worth rehearsing
Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.
Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.
When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.
Related guides
- useCopyToClipboard HookCopy text with navigator.clipboard, fallback execCommand, transient copied state, and permissions that fail in insecure contexts.
- useDebouncedValue HookDebounce a changing value in React without firing effects on every keystroke. Cleanup, leading vs trailing, and the stale timer that updates unmounted state.
- useEventListener HookAttach a window, document, or element listener with a stable handler ref so you do not rebind on every render. Capture option and target changes.
- useFetch With AbortControllerFetch in an effect with a signal, ignore aborted errors, and prevent the slow response from winning a race after the url changes.
- useIdle HookDetect idle users: reset a timer on input events, fire idle after timeout, and decide whether mount starts the timer immediately.