Skip to the chapter
←ReactGGroundwork⚛React
React/Advanced/A8
41 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛A8 · Part 3 · Advanced

Effect timing

Four hooks that all run 'after render', at four different moments.

4 min read8 sections2 exercises
41 of 57

In this chapter

  • The order, precisely
  • See where your code lands
  • When useLayoutEffect is the right answer
  • Cleanup timing
  • Effects and refs

and 3 more

about 4 min
08On this page
  1. The order, precisely
  2. See where your code lands
  3. When useLayoutEffect is the right answer
  4. Cleanup timing
  5. Effects and refs
  6. The order that surprises people
  7. Strict Mode's double invocation
  8. Escaping the timing entirely

The order, precisely

  1. React renders — your components run, producing the next tree.
  2. Commit: the DOM is mutated. Refs are attached.
  3. useLayoutEffect cleanups run, then its effects. Synchronously.
  4. React yields; the browser paints.
  5. useEffect cleanups run, then its effects.

Within each phase, children run before parents — a child's effect fires before its parent's, because a parent may depend on its children having set themselves up.

HookRunsBlocks paintFor
useEffectafter paintnoalmost everything
useLayoutEffectafter DOM, before paintyesmeasuring and adjusting
useInsertionEffectbefore DOM mutationsyesinjecting styles — CSS-in-JS only
Ref callbacksduring commityesreacting to a node existing

See where your code lands

One update, five phases

When useLayoutEffect is the right answer

useLayoutEffect(() => {
  const { height } = ref.current.getBoundingClientRect();
  setTooltipTop(anchorTop - height);       // before the user sees it
}, [content]);

With useEffect, the tooltip paints in the wrong place, then jumps. With useLayoutEffect, React blocks the paint, applies the correction, and the browser draws once — the intermediate state never reaches the screen.

The rule: if the user would see a flicker, it is a layout effect. Measuring, scroll restoration, positioning a popover. Everything else — fetching, subscriptions, logging, timers — is a plain effect, because blocking paint for them is pure cost.

It warns during server rendering

useLayoutEffect cannot run on the server, so React warns when a component using it is server-rendered. Either the work belongs in useEffect, or the component should not render on the server. The common workaround — aliasing to useEffect on the server — hides the warning without fixing the flicker.

Cleanup timing

useEffect(() => {
  connect(roomId);
  return () => disconnect(roomId);
}, [roomId]);

Change roomId from A to B and the order is: disconnect(A), then connect(B). The cleanup closes over the render that created it, so it disconnects the room it actually connected to — not the new one. That is why a cleanup reading current state rather than its own captured values is a bug.

Effects and refs

function Chart() {
  const ref = useRef(null);
  useEffect(() => {
    const chart = new ChartLib(ref.current);   // ✓ node exists by now
    return () => chart.destroy();
  }, []);
  return <div ref={ref} />;
}

Refs are attached during commit, so any effect can rely on ref.current. During render it is null, which is why DOM work never belongs in the component body.

The order that surprises people

Parent renders
  Child renders
Child effect runs        // ← child first
Parent effect runs

A parent effect that measures a child can rely on the child having mounted and laid out. The reverse is not true: a child effect cannot assume its parent has finished setting up.

Strict Mode's double invocation

In development React mounts, runs effects, runs cleanups, and runs the effects again. Anything that is not idempotent shows immediately — two socket connections, a doubled analytics event, two intervals. It is a detector, not a bug, and the same failure would appear in production the first time a user navigated away and back.

Until React 19.3, a root that was hydrated from server HTML skipped this double run, while a client-rendered root did it — so an effect bug could hide in an SSR app and show in a client-only one. Since 19.3 Strict Mode double invokes effects during hydration too. An upgrade can therefore surface a doubled socket or analytics call that was always there.

Escaping the timing entirely

Effect-ordering questions are often a sign that the effect should not exist. Before reaching for useLayoutEffect, check whether the value can be derived during render, whether a key would reset the thing you are trying to synchronise, or whether CSS can do the positioning. The best fix for a timing problem is usually to remove the timing.

Interview answer, one sentence

"useLayoutEffect runs after the DOM is updated but before paint, so it is for measuring and correcting layout where a plain effect would flicker; everything else belongs in useEffect, which runs after paint and does not block it. Within either phase children run before parents, and a cleanup sees the values of the render that created it."

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsPredict the firing orderadvanced
  • 5 testsProve the ordering rule with real React, not just a descriptionadvanced
All 54 for this level →
A8

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. A7Previous · A7Rendering strategies
  2. A8you are here
  3. Next · A9useSyncExternalStoreThe correct way to read state that lives outside React.5 min read

01On this page

8 sections · 0%

  1. 01The order, precisely
  2. 02See where your code lands
  3. 03When useLayoutEffect is the right answer
  4. 04Cleanup timing
  5. 05Effects and refs
  6. 06The order that surprises people
  7. 07Strict Mode's double invocation
  8. 08Escaping the timing entirely

[] chapters · t top