skip to content

In a React tree, a parent component and its child both register callbacks with useEffect. On mount, whose callback runs first, and when a single commit changes effects in several components, how does React order the cleanups against the new callbacks?

level: middleimportance: should knowfreq 42%

answer

  1. not the order components rendered
  2. deepest finishes first
  3. parent effect sees a ready subtree
  4. two passes, never interleaved
  5. teardown for all, then setup for all

basics

~20 s

The child's callback runs first: React runs effects bottom-up, deepest component first, so a parent's effect sees fully mounted children. Within one commit React runs every affected component's cleanup before running any of the new callbacks.

solid answer

~50 s

Effects fire bottom-up. React completes a child's work before its parent's during render, and the commit walks that same order, so on mount the child's `useEffect` callback runs before the parent's — which is what you want, because a parent's effect can then assume its children's DOM and subscriptions already exist. The same ordering applies to layout effects and to `componentDidMount`. When a commit updates effects in several components at once, React does two separate passes over the tree: it runs all the cleanup functions first, then all the new callbacks. It never interleaves them per component. That matters because a cleanup in one component and a setup in another may touch the same external resource; running every teardown before any setup keeps that resource from being subscribed twice or torn down after it was re-established.

go deeper

for a junior

Recall that effects run after React has updated the DOM and that the innermost component's effect runs first; be able to demonstrate it with two console logs rather than guessing.

for a middle

Explain why the order is bottom-up — a child's work completes before its parent's, and a parent's effect is only meaningful once its subtree is set up — and state the cleanups-then-setups rule for a commit.

for a senior

Use the guarantee when designing effects that share an external resource, and be able to say which orderings are contractual (parent/child) and which are incidental (siblings) so reviews do not enshrine the latter.

for a principal

Push teams away from cross-component effect choreography altogether: ordering guarantees are a weak coordination mechanism, so prefer explicit registration APIs or shared state where the dependency is real and visible.

## The ordering rule React runs effects **child before parent**. If `<Parent>` renders `<Child>` and both call `useEffect`, the console shows the child's message first on mount. The same holds for `useLayoutEffect` and for `componentDidMount` in class components. ```jsx function Child() { useEffect(() => console.log('child'), []); return null; } function Parent() { useEffect(() => console.log('parent'), []); return <Child />; } // logs: child, then parent ``` This surprises people who expect top-down order, because the component *functions* were called top-down: React had to run `Parent` before it even knew `Child` existed. ## Why bottom-up Two reasons, one mechanical and one semantic. Mechanically, React's render walk descends into a subtree and only finishes a node after all of its descendants are finished. The list of work the commit has to perform is therefore assembled in completion order, which is deepest-first. The commit walks that list. Semantically, it is the only order that makes a parent's effect meaningful. An effect is where you touch the outside world: measure a node, focus an input, start a subscription, register a child with a controller. A parent frequently coordinates its children — it may want to measure the whole subtree, or wire up children that registered themselves during their own effects. If parents ran first, every such effect would run against a subtree that had not finished setting itself up. The reverse dependency almost never exists: a child rarely needs its parent's effect to have run. Note that the order is a **tree** order, not a source order. It does not matter which component's `useEffect` call appears first in a file, or which file loaded first. Within a single component, though, source order does hold: multiple `useEffect` calls in one component run in the order they are written. ## Cleanups first, then setups The second half of the rule concerns updates. Suppose a commit changes the dependencies of effects in three different components. React does **not** walk the tree once, doing "cleanup A, setup A, cleanup B, setup B". It makes two passes: one that runs every pending cleanup function across the affected tree, then one that runs every new callback. This is a deliberate guarantee, not an implementation accident, and it matters whenever effects share an external resource. Imagine a component that unmounts while a sibling mounts, and both subscribe to the same event bus with a keyed handler. With interleaved ordering, the mounting sibling could register its handler and then have the unmounting one's cleanup remove it — an unsubscribe that silently kills a live subscription. Running all teardown before any setup removes that whole class of bug. The same two-pass structure appears at both timings. Layout-effect cleanups run during the mutation sub-phase and layout-effect setups during the layout sub-phase, which are already two separate passes. Passive effects mirror it: React tears down all pending passive effects across the tree, then sets up the new ones. ## Consequences you can be asked about **Coordination is fragile if you rely on order across siblings.** Sibling order follows tree position, so a refactor that moves a component changes when its effect runs relative to another. Treat cross-component effect ordering as an implementation detail unless it is a genuine parent/child relationship. **"My parent's effect sees a null ref" is usually a different bug.** Refs are attached during the commit's layout sub-phase, before any layout effect and well before passive effects, so by the time a parent's effect runs both its own ref and its children's are populated. If a ref is `null` there, the node is conditionally rendered or the ref was passed to a component that never forwarded it to a DOM element. **Logging order is the cheap way to verify.** Because these are ordering guarantees rather than heuristics, a couple of `console.log` calls settle any argument about what runs when in a specific tree. ## The short answer to give "Effects run bottom-up — children before parents — so a parent's effect can assume its subtree is set up; and within one commit React runs all cleanups before any new effect bodies, so two components never fight over the same external resource." That sentence carries both halves of the rule and the reason for each.

  • If one component calls useEffect three times, what decides the order of those three callbacks?
    Source order within the component. Hooks are stored on the fiber in call order, and React runs that component's effects in the same sequence on every commit. This is the one place where where you wrote the call actually determines timing — across components, tree position decides.
  • Does the same child-before-parent rule apply to useLayoutEffect?
    Yes. Layout effects follow the identical tree order; the only difference is when the pass happens — inside the commit, before the browser paints, rather than on a scheduled callback afterwards. So a parent's layout effect can measure a subtree whose children's layout effects have already run.
  • Why is it risky for a component's effect to depend on a sibling's effect having already run?
    Because sibling ordering is derived from position in the tree, and any refactor that reorders or re-nests components silently changes it. Parent/child ordering is a guarantee worth relying on; sibling ordering is an incidental consequence. Real dependencies between siblings belong in shared state, a ref set by a parent, or an explicit registration API.

saying these in an interview costs you the question

  • Says parent effects run before child effects
  • Assumes effects follow the order components rendered
  • Thinks a component's cleanup runs right before its own new callback
  • Believes source order across files decides effect order
  • Claims effect order is undefined and unreliable

context