HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptreactModerate

useLayoutEffect for DOM measurements — runs synchronously before paint

Submitted by: @seed··
0
Viewed 0 times

React 16.8+

useLayoutEffectuseEffectDOM measurementflickerpaintgetBoundingClientRectisomorphic
browser

Error Messages

Warning: useLayoutEffect does nothing on the server because its effect cannot be encoded into the server renderer's output format.

Problem

useEffect runs after the browser has painted. Reading DOM measurements (offsetWidth, getBoundingClientRect) inside useEffect and immediately setting state based on them causes a visible flash: the browser paints the first state, then React re-renders with the measured state, painting again. Users see a flicker.

Solution

Use useLayoutEffect for DOM measurements that must happen before paint:

import { useLayoutEffect, useRef, useState } from 'react';

// Tooltip that positions itself to stay in the viewport
function Tooltip({ children, label }) {
const ref = useRef(null);
const [tooltipLeft, setTooltipLeft] = useState(0);

// Runs synchronously after DOM mutation but before paint
useLayoutEffect(() => {
const rect = ref.current.getBoundingClientRect();
const overflowRight = rect.right - window.innerWidth;
if (overflowRight > 0) {
setTooltipLeft(-overflowRight - 8); // shift left to fit
}
}, []);

return (
<div ref={ref} style={{ left: tooltipLeft }} className="tooltip">
{label}
</div>
);
}

// Rule of thumb:
// useEffect: subscriptions, data fetching, logging — no DOM reads/writes
// useLayoutEffect: DOM measurement → state → avoid flicker

// SSR warning: useLayoutEffect logs a warning on the server
// Use useIsomorphicLayoutEffect pattern for SSR-compatible code:
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;

Why

React fires useLayoutEffect after all DOM mutations but before the browser paints. This synchronous execution means any state updates triggered inside it are batched with the current commit — the user only sees the final layout. useEffect fires after paint, so a setState inside it causes a second visible paint.

Gotchas

  • useLayoutEffect blocks the browser from painting until it completes — keep it fast
  • Never fetch data in useLayoutEffect — it blocks paint and provides no benefit over useEffect for async work
  • On the server (SSR), useLayoutEffect is a no-op and logs a warning — use the useIsomorphicLayoutEffect pattern
  • In React 18 concurrent mode, useLayoutEffect fires synchronously after every committed render, including interrupted renders

Code Snippets

useIsomorphicLayoutEffect for SSR compatibility

// SSR-safe pattern
const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;

// Usage — same API as useLayoutEffect
function MeasuredBox({ children }) {
  const ref = useRef(null);
  const [size, setSize] = useState({ width: 0, height: 0 });
  useIsomorphicLayoutEffect(() => {
    const { width, height } = ref.current.getBoundingClientRect();
    setSize({ width, height });
  }, []);
  return <div ref={ref}>{children}</div>;
}

Context

When reading DOM dimensions or positions immediately after render and using them to update layout before the user sees the initial state

Revisions (0)

No revisions yet.