useLayoutEffect

useLayoutEffect has the identical signature to useEffect and one difference in timing: React 7,897 runs it after the DOM has been mutated but before the browser paints, and any state update inside it is flushed in the same frame. The user never sees the intermediate layout.

That matters when a component has to measure something it just rendered and then correct itself — a tooltip that must sit above its target, a list that scrolls to the bottom, a box sized to its content. With useEffect, the first layout is painted, then the corrected one, and the element visibly jumps.

A tooltip that measures itself before the browser paintsHTML
<div id="root"></div>
<style>.tip{position:absolute;background:#37474f;color:#fff;padding:4px 8px}</style>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@8.0.5/babel.min.js"></script>
<script type="importmap">
{ "imports": { "react": "https://esm.sh/react@19.3.0?dev",
  "react-dom/client": "https://esm.sh/react-dom@19.3.0/client?dev" } }
</script>
<script type="text/babel" data-type="module">
  import React, { useRef, useState, useLayoutEffect } from 'react';
  import { createRoot } from 'react-dom/client';
  function Tooltip({ rect, children }) {
    const ref = useRef(null);
    const [top, setTop] = useState(0);
    useLayoutEffect(() => {
      const h = ref.current.getBoundingClientRect().height;
      setTop(rect.top - h - 6);        // measured, re-rendered, then painted once
    }, [rect]);
    return <div className="tip" ref={ref} style={{top, left: rect.left}}>{children}</div>;
  }
  function App() {
    const btn = useRef(null), [rect, setRect] = useState(null);
    return <div style={{padding: '60px 16px'}}>
      <button ref={btn} onClick={() => setRect(btn.current.getBoundingClientRect())}>
        Show tooltip</button>
      {rect && <Tooltip rect={rect}>Placed above, never seen at top 0</Tooltip>}
    </div>;
  }
  createRoot(document.getElementById('root')).render(<App />);
  setTimeout(() => document.querySelector('button').click(), 400);   // simulate a click
</script>
Browser output of Listing 2.29
Browser output of 29

The cost is real: layout effects block painting, so a slow one freezes the frame, and they cannot run during server rendering, which makes React warn. Reach for useEffect first and switch only when a measurement has to happen before the user sees the result. useInsertionEffect, which runs even earlier, exists for CSS-in-JS libraries injecting <style> rules and is not meant for application code.