Rendering & Reconciliation
Render is not “update the DOM”
Section titled “Render is not “update the DOM””A common misconception: “re-render” means “React touches the DOM.” It doesn’t. React splits the work into two distinct phases:
flowchart LR trigger["state change / parent re-render"] --> render["Render phase (call components, build new tree) — pure, no DOM"] render --> recon["Reconciliation (diff new tree vs old)"] recon --> commit["Commit phase (apply minimal DOM changes) — side effects here"]
- Render phase — React calls your components to build a new tree of elements. This is pure computation: no DOM is touched, and React can pause or throw it away.
- Commit phase — React compares the new tree to the previous one, computes the minimal set of DOM operations, and applies them.
So a re-render that produces the same output as before results in zero DOM changes. Rendering is cheap-ish; the expensive part (touching the DOM) only happens for what actually changed.
Reconciliation: diffing two trees
Section titled “Reconciliation: diffing two trees”Comparing two arbitrary trees is famously expensive (O(n³)). React makes it fast (O(n)) with two heuristics:
- Different type → throw it away. If an element’s
typechanged (<div>became<span>, orComponentAbecameComponentB), React unmounts the old subtree entirely and builds the new one fresh — including destroying its state. - Same type → keep the node, update props. If the type is the same, React keeps the existing DOM node and its state, and just updates the changed props.
// Same type across renders → the <input> keeps its DOM node and its focus/value.{isEditing ? <input value={text} /> : <input value={text} disabled />}
// Different type → React destroys the first and mounts the second (state lost).{isEditing ? <input value={text} /> : <p>{text}</p>}Keys: identity for lists
Section titled “Keys: identity for lists”For a list of siblings of the same type, React needs to know which item is which across renders. By default it matches by position (index) — which breaks the moment items reorder, insert, or delete. A key gives each item a stable identity so React can track it.
// ✅ Stable identity — React follows each todo even when the list reorders.{todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)}
// ⚠️ Index as key — reorder/insert/delete and React mismatches items,// corrupting per-item state (a checked box jumps to the wrong row).{todos.map((todo, i) => <TodoItem key={i} todo={todo} />)}flowchart TB before["Before: [A, B, C] keys a,b,c"] -->|prepend Z| after["After: [Z, A, B, C] keys z,a,b,c"] after --> good["With stable keys: A,B,C reused, Z mounted"] before2["Before: [A, B, C] key = index 0,1,2"] -->|prepend Z| after2["After: index 0,1,2,3"] after2 --> bad["With index keys: every item mismatched, state shifts to wrong rows"]
The rule: a key must be stable, unique among siblings, and tied to the data’s identity (usually a database id) — never the array index for a list that can change, and never Math.random() (which changes every render and forces a full remount).