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
| Concept | Rule |
|---|---|
| Render step | Pure calculation; no DOM mutation happens here |
| Commit step | Applies only the minimal DOM changes that actually differ |
| State + position | Same component type, same tree position → state preserved across prop changes |
| State + type change | Different component/element at that position → state reset |
| List key | Ties state to the actual data item, not its current position |
Syntax
{contacts.map((contact) => (
<Contact key={contact.id} contact={contact} /> // stable, data-based key
))}Examples
// 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.