Effects & Lifecycle

useLayoutEffect

Fires synchronously after all DOM mutations but before the browser paints. Used for reading layout and synchronously re-rendering to avoid visual flicker.

Signature

TypeScript
useLayoutEffect(setup: () => (() => void) | void, dependencies?: any[])

Parameters

ParameterTypeDescription
setup() => (() => void) | voidA function that runs synchronously after DOM updates. May return a cleanup function.
dependenciesany[]Optional dependency array. Behaves identically to useEffect dependencies.

Return Value

undefined — useLayoutEffect does not return a value.

Examples

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

function Tooltip({ text, children }: { text: string; children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);
  const [pos, setPos] = useState({ top: 0, left: 0 });

  useLayoutEffect(() => {
    if (ref.current) {
      const rect = ref.current.getBoundingClientRect();
      setPos({ top: rect.top - 30, left: rect.left + rect.width / 2 });
    }
  });

  return (
    <div ref={ref} style={{ position: 'relative', display: 'inline-block' }}>
      {children}
      <div style={{
        position: 'fixed',
        top: pos.top,
        left: pos.left,
        transform: 'translateX(-50%)',
        background: '#333',
        color: '#fff',
        padding: '4px 8px',
        borderRadius: 4,
        fontSize: 12,
      }}>
        {text}
      </div>
    </div>
  );
}
Auto-Resize Textarea
import { useRef, useLayoutEffect } from 'react';

function AutoResizeTextarea({ value, onChange }: {
  value: string;
  onChange: (v: string) => void;
}) {
  const ref = useRef<HTMLTextAreaElement>(null);

  useLayoutEffect(() => {
    const el = ref.current;
    if (el) {
      el.style.height = '0px';
      el.style.height = el.scrollHeight + 'px';
    }
  }, [value]);

  return (
    <textarea
      ref={ref}
      value={value}
      onChange={e => onChange(e.target.value)}
      style={{ overflow: 'hidden', resize: 'none' }}
    />
  );
}
Scroll Restoration
import { useRef, useLayoutEffect } from 'react';

function ChatMessages({ messages }: { messages: { id: string; text: string }[] }) {
  const endRef = useRef<HTMLDivElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);

  useLayoutEffect(() => {
    endRef.current?.scrollIntoView({ behavior: 'instant' });
  }, [messages.length]);

  return (
    <div ref={containerRef} style={{ maxHeight: 400, overflow: 'auto' }}>
      {messages.map(m => <div key={m.id}>{m.text}</div>)}
      <div ref={endRef} />
    </div>
  );
}

Common Pitfalls

!

Using useLayoutEffect for non-layout work — it blocks painting and can degrade performance. Prefer useEffect for most side effects.

!

Running expensive computations inside useLayoutEffect that delay the browser paint, causing jank.

!

Using useLayoutEffect on the server — it doesn't run during SSR and will emit a warning. Use useEffect as a fallback for SSR-safe code.

!

Not including the correct dependencies, causing the layout measurement to be stale.

Understanding useLayoutEffect

useLayoutEffect is a variant of useEffect that fires synchronously after React has performed all DOM mutations but before the browser repaints the screen. This timing is critical for operations that need to read layout information and make visual adjustments before the user sees the result — such as measuring element dimensions, repositioning tooltips, or preventing scroll jumps.

The key difference from useEffect is timing. useEffect runs asynchronously after the browser has painted, which means users might see a brief flicker if the effect changes the DOM. useLayoutEffect prevents this by running before the paint, guaranteeing that the user sees the final state without any intermediate visual artifacts. This makes it essential for layout measurements and synchronous DOM manipulations.

However, this synchronous behavior comes at a cost. Because useLayoutEffect blocks the browser from painting, any expensive computation inside it directly delays what the user sees. If your effect doesn't need to measure or mutate the DOM before paint, use useEffect instead. The React team recommends starting with useEffect and switching to useLayoutEffect only when you observe visual flickering.

In server-side rendering environments, useLayoutEffect causes a warning because it cannot run on the server (there's no DOM to measure). The common workaround is to use useEffect for SSR-compatible code or to conditionally use useLayoutEffect only in the browser. Some libraries provide a useIsomorphicLayoutEffect utility that selects the appropriate hook based on the environment.

Related Hooks

More Effects & Lifecycle Hooks

Explore All React Hooks

Browse our complete reference of 19 React hooks with signatures, examples, pitfalls, and in-depth explanations.