Skip to content

useEffect vs useLayoutEffect in React

Both useEffect and useLayoutEffect are React hooks for running side effects, but they differ in when they run in React’s rendering lifecycle.

The Core Difference is:

useEffect runs after the browser has painted the screen, while useLayoutEffect runs before the browser has painted the screen.

This timing difference is the source of all their other distinctions.

useEffect

useEffect runs asynchronously after the browser has painted the screen.

Use cases: most side effects, data fetching, setting up subscriptions and event listeners.

Since it does not block the main thread, it is more performant.

Example:

Here, React paints the screen with the new count first, then updates the document title.

useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]);

useLayoutEffect

useLayoutEffect runs synchronously after DOM mutations but before the browser paints.

useLayoutEffect is best for reading layout from the DOM and synchronously re-rendering. This is useful when you need to measure a DOM element (like its size or scroll position) and then change something based on that measurement.

useLayoutEffect prevents a visual “flicker” where the user first sees the initial render and then the updated state immediately after. For example, calculating a tooltip’s position and then setting its top and left styles.

Example:

Here, React updates the DOM, then immediately runs this hook before the user sees anything.

useLayoutEffect(() => {
  const el = ref.current;
  const { height } = el.getBoundingClientRect();
  console.log("Height:", height);
}, []);

Practical Example: Preventing Flicker

Let’s say you have a tooltip component that needs to be positioned correctly. You can use useLayoutEffect to measure the tooltip’s size and position it correctly before the browser paints.

function Tooltip() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const tooltipRef = useRef(null);

  // ❌ useEffect - might cause flicker
  useEffect(() => {
    const rect = tooltipRef.current.getBoundingClientRect();
    setPosition({ x: rect.width, y: rect.height });
  }, []);

  // ✅ useLayoutEffect - no flicker
  useLayoutEffect(() => {
    const rect = tooltipRef.current.getBoundingClientRect();
    setPosition({ x: rect.width, y: rect.height });
  }, []);

  return (
    <div ref={tooltipRef} style={{ left: position.x, top: position.y }}>
      Tooltip
    </div>
  );
}

Summary

useEffect: async, runs after paint (doesn’t block the UI).

useLayoutEffect: sync, runs before paint (can block UI if heavy).

Use useEffect in most of the cases. Only switch to useLayoutEffect if you notice visual problems where you need to measure DOM elements or update styles/layout before the user sees it.