AniUI Academy

The Virtual DOM and Reconciliation

What actually happens between a state change and a pixel changing on screen — the render phase, the diffing algorithm's real rules, and why they explain the key prop's behavior.

10 min read

You've been told, informally, that changing state causes a re-render. This lesson makes that precise: what actually happens, in what order, and why the key prop from an earlier lesson matters so much.

Elements are descriptions, not DOM nodes

Recall from the JSX lesson: a React element is a plain JavaScript object — { type: "button", props: { children: "Save" } }, roughly — not a real DOM node. Creating one is cheap: no browser layout, no paint, just an object allocation. This matters because React's whole strategy depends on being able to create these descriptions freely and compare them, rather than touching the actual browser every time something might have changed.

Two phases: render, then commit

When state changes, React does its work in two distinct phases:

  1. Step 1

    Render

    React calls your component functions again, producing a new tree of elements — a description of what the UI should now look like.

  2. Step 2

    Reconcile

    React compares ("diffs") the new tree against the previous one, and works out the minimal set of real DOM changes needed.

  3. Step 3

    Commit

    React applies exactly those changes to the actual DOM — and only now does anything the browser can see actually happen.

  4. Step 4

    Paint

    The browser takes over from here and paints the updated pixels to the screen, same as after any other DOM mutation.

Nothing touches the real DOM until the commit step — render and reconcile are pure bookkeeping over plain JS objects.

The practical consequence: the render phase (your component function running) must be free of side effects — no DOM mutation, no starting a fetch, no logging you rely on happening exactly once. React may call your component function more than once for the same update in some circumstances (this is deliberate and part of how React can prepare work without committing to it), so anything that must happen exactly once — a fetch, a subscription — belongs in an effect, covered in the next part of this course, which runs after commit.

The diffing algorithm's real rule

Comparing two full trees of arbitrary size, node by node, in the general case is an expensive problem. React makes it fast with one deliberate, pragmatic assumption: elements of a different type at the same position are treated as entirely different things, and elements of the same type at the same position are assumed to represent the same conceptual thing and get patched in place.

// Render 1
<div><UserProfile /></div>
 
// Render 2 — same type (div, then UserProfile) at each position
<div><UserProfile /></div>

Same types at the same positions → React keeps the existing DOM nodes and UserProfile's existing internal state, and only updates what actually changed inside it (text, attributes).

// Render 1
<div><UserProfile /></div>
 
// Render 2 — different type at the UserProfile's position
<div><GuestBanner /></div>

Different type at that position → React tears down the old subtree completely (running any cleanup) and builds the new one from scratch, with fresh state. This is why conditionally rendering different component types at the same spot in the tree resets state, while conditionally changing props on the same component type does not.

Why keys change the outcome

Within a list, siblings are normally matched by position — first old child compared to first new child, and so on. A key overrides that: React matches children by key instead of by position, across the whole list.

That's precisely why removing the first item from an unkeyed (or index-keyed) list of inputs can make the wrong input keep its typed text — positional matching thinks "position 0 is still position 0," when the identity of what actually lives there changed. A stable, data-based key tells React the truth: "this specific input moved from position 0 to position -1 (removed)," so React can update the right node.

Try it yourself

This demonstrates the type-switch behavior directly: two components that each hold their own count, swapped by changing the type rendered at the same position. Click to increment, then toggle — the count resets, because swapping type destroys and recreates the subtree, exactly as described above.

Try it yourself
Loading playground...

What to remember

  • An element is a cheap, plain-object description of UI — not a DOM node — which is what makes frequent re-rendering affordable.
  • Render produces new descriptions and figures out what changed; only commit touches the real DOM; paint is the browser's own step after that.
  • Same type at the same tree position is patched in place, preserving state; different type at the same position tears down and rebuilds from scratch, resetting state.
  • Keys override position-based matching within a list, which is why a stable key (not array index, for changing lists) prevents state from attaching to the wrong item.

Check yourself

4 questions · pass 3/4 to unlock useState and State Updates

up to 50
  1. 1.What is a React element, in relation to the real DOM?

  2. 2.In the render-then-commit model, what happens in the render phase?

  3. 3.By default, when a parent component re-renders, what happens to its children?

  4. 4.Two renders both produce a <button> at the same position in the tree, but with different text. What does React do?

4 left to answer