Cloud Tech by Victor
FrontendAdvanced

React Rendering & Reconciliation

Why React associates a component's state with its position in the tree, not the component instance, and why using an array index as a list key silently attaches the wrong state to the wrong item once the list reorders.

Updated 2026-07-243 min read

Overview

React's update process runs in three steps, trigger (a state change or initial render), render (a pure calculation of what the new JSX should be, with no DOM mutation at all), and commit (applying only the minimal actual DOM changes that differ from the previous render). What decides whether a component's state survives a re-render is its position in the render tree, not the component instance or its props, the same component type at the same tree position keeps its state across a prop change, but a different type at that same position gets a fresh start. This becomes a real, common bug in lists: keying by array index ties each item's state to a position rather than to the actual data, so reordering a list, say reversing it, silently reattaches state to the wrong item, because React never sees anything telling it which item is which independent of where it currently sits.

Quick Reference

ConceptRule
Render stepPure calculation; no DOM mutation happens here
Commit stepApplies only the minimal DOM changes that actually differ
State + positionSame component type, same tree position → state preserved across prop changes
State + type changeDifferent component/element at that position → state reset
List keyTies state to the actual data item, not its current position

Syntax

jsx
{contacts.map((contact) => (
  <Contact key={contact.id} contact={contact} /> // stable, data-based key
))}

Examples

jsx
// key={i} ties each item's state to its position, not the contact;
// reversing the list reattaches state to whichever item now sits
// at that same index.
{contacts.map((contact, i) => (
  <li key={i}>          {/* [!code highlight] */}
    <Contact contact={contact} />
  </li>
))}

// Fixed: key by the contact's own stable id instead.
{contacts.map((contact) => (
  <li key={contact.id}>
    <Contact contact={contact} />
  </li>
))}

A key can also force a state reset intentionally

Giving two different renders of the same component type different explicit keys (<Counter key="Taylor" /> vs <Counter key="Sarah" />) tells React they're conceptually different, resetting state deliberately, the same mechanism that causes accidental resets can be used on purpose.

Visual Diagram

Common Mistakes

  • Using an array index as a list key on data that can reorder, get filtered, or have items inserted/removed, silently misattaching state across renders.
  • Assuming a prop change alone resets a component's state, when what actually matters is whether the same component type stays at the same tree position.
  • Expecting rendering to touch the DOM directly, then being confused by behavior that only makes sense once you know DOM updates are deferred to a separate commit step.
  • Not realizing that swapping which component renders at a given position (even conditionally) resets that position's state, even when it "looks like" a small UI change.

Performance

  • Because commit only applies the minimal diff, not a full re-render of the DOM, most state updates are cheap regardless of how large the surrounding tree is.
  • A stable, data-based key lets React correctly reuse existing DOM nodes and component instances across a reorder instead of tearing down and recreating them, which is both a correctness and a performance win together.

Best Practices

  • Key list items by a stable, unique identifier from the actual data (an id), never by array index, for any list that can reorder, filter, or change.
  • Reason about state preservation via tree position and component type, not just "did the props change."
  • Use an explicit, deliberately different key when you want two renders of the same component type to be treated as genuinely separate instances with independent state.
  • Remember rendering itself never touches the DOM; if debugging seems to require reasoning about "what's on screen right now," think in terms of the last committed result, not the current render pass.

Interview questions

Does React mutate the DOM during the render step, and if not, when does the actual DOM update happen?

No. Rendering is a pure calculation, React calls component functions to figure out what the new JSX should be and diffs it against the previous render, but no DOM node is touched during this step. The actual DOM update happens in a separate commit step afterward, where React applies only the minimal necessary changes based on what actually differs from the previous render, appending everything on the very first render, but patching selectively on every re-render after that. This separation is why an uncontrolled `<input>`'s typed value survives a re-render of its parent: React only touches DOM nodes that actually changed, and leaves everything else alone.

A component's state is preserved when a prop changes, but reset when a completely different element renders in the same spot. What determines which happens?

React associates state with a component's position in the render tree, not with the component instance or its props specifically. If the same component type renders at the same tree position across a re-render, its state is preserved regardless of what props changed. If a different component type (or a different element entirely) renders at that same position, React treats it as a genuinely different thing, destroys the old state, and starts fresh. This is why toggling a prop on the same `<Counter />` keeps its count, but swapping `<Counter />` for a `<p>` at that same spot in the tree resets it entirely, even though from the JSX it might look like a small, local change.

Why does using an array index as a list item's key cause state to attach to the wrong item once the list is reordered, and what's the fix?

With `key={i}`, React tracks each item's state by its position in the array, not by which real-world item it represents, so after reversing a list, the state that was originally at index 0 (say, an "expanded" toggle on the first item) stays at index 0 and now renders alongside whatever item ended up there after the reorder, not the original item it belonged to. The fix is using a stable, unique identifier from the actual data, `key={contact.id}`, so React can match each item to its correct state across renders regardless of how the list is reordered, added to, or filtered, rather than relying on a position that can shift under the data.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement