← Back to all posts

Understanding useEffect vs. useLayoutEffect in React

·Anmol Thukral

Choosing the right effect hook is key to avoiding subtle bugs and UI jank in React applications. The fundamental difference between useEffect and useLayoutEffect is their timing relative to the browser paint cycle. Grasping this mental model will lead to smoother user interfaces.

useEffect (The Default): Runs AFTER Paint

This is the standard side-effect hook. Its execution is deferred until after the browser has rendered the screen.

Step-by-Step Flow:

  1. React commits DOM changes. (New state is in the DOM.)
  2. Browser paints. (User sees the new frame.)
  3. useEffect callbacks fire. (Your code runs.)

Impact:

  • Any DOM reading or mutation inside useEffect happens on the next visual frame.
  • Risk of Flicker: The user might momentarily see the old layout before your effect runs and potentially causes a layout jump or "flash of wrong content."
  • Stale Measurements: If you read DOM properties like offsetWidth or scrollTop, you might get values from the previous frame.

When to Use useEffect (The Default):

  • Non-DOM-related side effects: Data fetching, logging, setting up subscriptions, timers, or analytics calls.
  • The vast majority of side effects.
  • When there is no visible flicker or layout issue.

Example Use:


useEffect(() => {
  // Doesn't need to block the paint cycle
  fetchUser(id).then(setUser);
}, [id]);

useLayoutEffect: Runs BEFORE Paint (Synchronous)

This hook is for interacting with the DOM immediately after React has made its changes, but before the browser paints the new frame.

Step-by-Step Flow:

  1. React commits DOM changes. (New state is in the DOM.)
  2. useLayoutEffect callbacks fire. (Synchronous, blocks paint)
  3. Browser does layout and paint. (User sees the final frame.)

Impact:

  • Perfect for when you must read or mutate the DOM and ensure the result is visible before the user sees the screen.
  • No Flicker: Ensures visual correctness by preventing layout shifts or stale measurements from being displayed.

The Trade-Off: Potential Jank

Because useLayoutEffect runs synchronously and blocks the main thread before the browser can paint, a long or slow operation inside it will delay the frame, potentially causing jank. It is a trade-off between visual correctness and performance risk.

When to Use useLayoutEffect (Only When Necessary)

Only use this hook when a DOM measurement or mutation is required to prevent a visual bug.

Good Reasons:

  • Scroll Position Fixes: Setting scrollTop or using scrollIntoView immediately after content changes (e.g., in a chat window).
  • Dynamic Positioning: Measuring an element's size/position to correctly place another element (e.g., tooltips, popovers, arrows).
  • Syncing DOM Elements: Calculating dimensions for sticky headers, fake scroll shadows, or other synchronization tasks.
  • Preventing Layout Shift: Guaranteeing the layout is correct before the first paint.

Bad Reasons (Stick to useEffect):

  • "I want it to run earlier" or "Just to be safe."
  • Data fetching.
  • Console logging.

Example Use:


useLayoutEffect(() => {
  if (!ref.current) return;
  // This mutation happens before paint, preventing a flash of the wrong scroll position
  ref.current.scrollTop = ref.current.scrollHeight;
}, [messages]);

Avert Performance Pitfalls

Avoid Layout Thrashing: Multiple useLayoutEffect calls in a component tree can force numerous synchronous layout recalculations (reflows), leading to a massive performance hit. Batch your measurements when possible.

The Golden Rule: If you do not see a flicker, wrong initial position, or stale measurements with useEffect, then do not use useLayoutEffect. It is inherently slower and riskier for performance.

Quick Decision Tree

  • Does it read or write DOM layout before the user sees it? Yes -> useLayoutEffect
  • Does it fetch data, log, subscribe, or anything non-DOM-layout? Yes -> useEffect
  • Is there no visual flicker/jump with the default hook? Yes -> useEffect (Default)
  • Can CSS handle the layout/animation instead? Yes -> Delete the effect entirely

Bonus Ninja Tip: Prioritize CSS solutions (Flexbox, Grid, position: sticky, native transitions) over JavaScript for layout and styling to minimize effect timing problems.

Master the timing model, respect the paint cycle, and write butter-smooth React UIs. 🥷