useLayoutEffect

useLayoutEffect

What it is in React

useLayoutEffect runs after DOM mutations for this commit but before the browser paints the user-visible frame (in the real browser integration). That lets you read layout (heights, scroll positions) and synchronously adjust DOM so users do not see a “wrong” intermediate frame.

useEffect (passive) runs later — good for subscriptions that do not need to block paint.

This lesson only contrasts same-turn synchronous read vs queueMicrotask read as a teaching sketch — not a real paint boundary.

React (reference)

useLayoutEffect(() => {
  const w = node.getBoundingClientRect().width;
  setMeasured(w);
}, []);

Vanilla implementation

After append, read getBoundingClientRect() immediately — that is the layout-effect-style path. Schedule the same read in a microtask to contrast with a later turn of the event loop (roughly closer to where a passive effect might run).

export function mountLesson08(host) {
  function paint() {
    host.replaceChildren();
    const box = document.createElement('div');
    box.textContent = 'Measured box';
    box.style.width = '140px';
    box.style.padding = '8px';
    box.style.border = '1px solid';
    host.append(box);

    const widthSync = box.getBoundingClientRect().width;

    const syncLine = document.createElement('p');
    syncLine.textContent = `Same-turn read (layout-ish): ${Math.round(widthSync)}px`;
    host.append(syncLine);

    queueMicrotask(() => {
      const widthLater = box.getBoundingClientRect().width;
      const asyncLine = document.createElement('p');
      asyncLine.textContent = `Microtask read (passive-effect-ish): ${Math.round(widthLater)}px`;
      host.append(asyncLine);
    });
  }

  paint();
  return () => host.replaceChildren();
}

Not in this lesson

Real host flush timing. See ../REQUIREMENTS.md §1.5.