Must knowHooks · 03

React Lifecycle

Mount, update, unmount: the recorded order of renders, layout effects, effects and cleanups.

On this page · 10 sections
  1. In one minute
  2. The example: a cart badge that logs everything
  3. What the recording shows
  4. Strict Mode
  5. The stages, summed up
  6. Server Components, briefly
  7. Common mistakes
  8. Interview Q&A
  9. Related
  10. Sources

In one minute

A component goes through three stages: mount (first appears), update (renders again because its state, its parent or a context it reads changed; any number of times) and unmount (removed). In every render React calls the whole function again, then commits the changes to the DOM, then runs effects. The details people get wrong are the order: an effect’s cleanup runs after the next render, not before it, and with the values of its own render; layout effects run before the browser paints, regular effects don’t hold the paint up.

You’ll be able to: say exactly what runs, in what order, on mount, update and unmount, and in Strict Mode.

CartBadge.tsx
1function CartBadge({ count }) {2  log(`render (count = ${count})`)3  useLayoutEffect(() => {4    log(`layout effect (count = ${count})`)5    return () => log(`layout cleanup (count = ${count})`)6  }, [count])7  useEffect(() => {8    log(`effect (count = ${count})`)9    return () => log(`effect cleanup (count = ${count})`)10  }, [count])11  return <span>{count} in cart</span>12}
What you see
(not on screen)
Console (recorded)
render (count = 0)
layout effect (count = 0)
effect (count = 0)
render (count = 1)
layout cleanup (count = 0)
layout effect (count = 1)
effect cleanup (count = 0)
effect (count = 1)
layout cleanup (count = 1)
effect cleanup (count = 1)
  1. render phaselog(`render (count = ${count})`)Mount. The function runs, the hooks are created, and JSX is returned.
  2. commit phaselog(`layout effect (count = ${count})`)React puts the <span> in the DOM, then runs layout effects before the browser paints, so they can measure or adjust without a flicker.
  3. effectslog(`effect (count = ${count})`)Then the regular effect. React doesn’t make the browser wait for it.
  4. render phaselog(`render (count = ${count})`)Update (count becomes 1). The function runs again first. Nothing has been cleaned up yet.
  5. commit phasereturn () => log(`layout cleanup (count = ${count})`)During the commit: the old layout effect’s cleanup (with count = 0, its own render’s value), then the new layout effect.
  6. effectsreturn () => log(`effect cleanup (count = ${count})`)Then the same for the regular effect. A cleanup always sees the values of the render that created it.
  7. commit phasereturn () => log(`layout cleanup (count = ${count})`)Unmount. No render: React runs the remaining cleanups one last time and removes the DOM.

0 / 0
A cart badge with one layout effect and one effect, each logging its count. Mount, one update, unmount: every line below was logged in this order.

The example: a cart badge that logs everything

function CartBadge({ count }: { count: number }) {
	log(`render (count = ${count})`)

	useLayoutEffect(() => {
		log(`  layout effect (count = ${count})`)
		requestAnimationFrame(() => log(`  next frame starts (count = ${count})`))
		return () => log(`  layout cleanup (count = ${count})`)
	}, [count])

	useEffect(() => {
		log(`  effect (count = ${count})`)
		return () => log(`  effect cleanup (count = ${count})`)
	}, [count])

	return <span id="badge">{count} in cart</span>
}

The page renders the badge, the recorder clicks “Add to cart” once, then “Hide badge”. Logged, phase by phase:

Mount

createRoot().render()
render (count = 0)
  layout effect (count = 0)
  next frame starts (count = 0)
  effect (count = 0)

Update (count 0 → 1)

render (count = 1)
  layout cleanup (count = 0)
  layout effect (count = 1)
  effect cleanup (count = 0)
  effect (count = 1)
  next frame starts (count = 1)

Unmount

  layout cleanup (count = 1)
  effect cleanup (count = 1)

What the recording shows

  1. The function runs first, cleanups later. On update, render (count = 1) is logged before effect cleanup (count = 0). React has to run the component to find out what changed; only when it commits does it clean up the previous effects and run the new ones. (A common diagram puts the cleanup before the re-render. The recording shows that’s not the order.)
  2. Cleanups see their own render’s values. The cleanup logged count = 0 even though the component was already rendering count = 1: it’s a closure from render 0.
  3. Layout effects come first and finish before paint. useLayoutEffect (and its cleanup) runs right after the DOM is updated, before the browser paints. Use it to measure or adjust layout without a visible flicker; keep it short, because the paint waits for it.
  4. Regular effects don’t block paint, but aren’t guaranteed to run after it. React lets the browser paint before running them when it can. After a discrete interaction like a click, React runs them right away to keep the UI consistent: here effect (count = 1) ran before the next frame started. Don’t rely on either timing.
  5. Unmount is cleanups only. No render happens; every remaining cleanup runs once, then the DOM is removed and the component’s state is gone.

Strict Mode

In development, <StrictMode> adds checks. The same mount, wrapped in it:

createRoot().render()
render (count = 0)
render (count = 0)
  layout effect (count = 0)
  effect (count = 0)
  layout cleanup (count = 0)
  effect cleanup (count = 0)
  layout effect (count = 0)
  effect (count = 0)
  next frame starts (count = 0)
  next frame starts (count = 0)

The component rendered twice (to catch impure renders), and the effects ran, were cleaned up, and ran again (to catch missing cleanups). None of this happens in production builds. If your effect breaks under this, it would also break when the component remounts for real.

The stages, summed up

Mount Update Unmount
Component function runs runs again (all of it) —
useState initial value (initializer runs) current value discarded
DOM created changed where needed removed
useLayoutEffect runs (before paint) old cleanup → new run, if deps changed cleanup
useEffect runs old cleanup → new run, if deps changed cleanup

Server Components, briefly

React Server Components (for example in Next.js’ App Router) run on the server only. They have no state and no effects, so none of this lifecycle applies to them; they render, their output is sent to the client, and a later request or a server action re-runs them. Client components inside them go through mount, update and unmount as above.

Common mistakes

Interview Q&A

What are the three stages of a component's lifecycle?

Mount (first render and commit), update (re-render because of its own state, its parent, or a context it reads; repeatable) and unmount (removal). Effects map onto them: they run after mount, re-run after updates where dependencies changed, and their cleanups run before re-runs and on unmount.

Does React re-run the whole component on every render?

Yes, top to bottom. What React limits is the output: it compares the new elements with the previous ones and changes only the DOM that differs.

In what order do render, cleanup and effect happen on an update?

Render first, then the commit: layout-effect cleanups, layout effects, and then effect cleanups and effects. Recorded: render (count = 1) → layout cleanup (count = 0) → layout effect (count = 1) → effect cleanup (count = 0) → effect (count = 1).

Why does a cleanup function see "old" values?

It’s a closure created during the render that ran the effect. The recording’s update logged effect cleanup (count = 0) while the new render had count = 1.

When would you use useLayoutEffect instead of useEffect?

When the effect reads or changes layout and the user must not see the intermediate state (measuring an element to position a tooltip). It runs before the browser paints, so keep it fast; everything else belongs in useEffect.

What does Strict Mode do to the lifecycle?

In development only, it calls component functions twice and runs mount effects as setup → cleanup → setup. Recorded: two render (count = 0) lines and the effect/cleanup/effect sequence.

Sources

Primary source: react.dev/learn/lifecycle-of-reactive-effects