TL;DR
- React is a rendering system, not just a component library.
- The core loop is render → reconcile → commit, and only the commit phase mutates the DOM.
- Component state sticks to identity, and identity is mostly tree position plus keys.
- Many surprising React bugs are really identity or lifecycle bugs, not syntax bugs.
Context
React gets much easier once I stop treating re-renders as magic. The moment I understand what React is trying to preserve, replace, or defer, the behavior becomes more predictable.
The Approach
This is the model I keep in my head:
Under the hood, modern React uses Fiber so render work can be represented as a traversable work tree rather than one long recursive synchronous pass. That matters because React can prepare work separately from the final DOM commit.
Three rules matter most to me:
- render should stay pure
- keys control identity inside lists
- unmounting destroys component-local state
{items.map((item) => (
<Row key={item.id} item={item} />
))}
If I use index as a key for a reordered list, I usually get state bugs, lost focus, or animation glitches.
I also watch conditional rendering carefully:
return showForm ? <CheckoutForm /> : <SuccessMessage />
If showForm flips, CheckoutForm unmounts and its local state disappears. Sometimes that is exactly what I want. Sometimes it is a bug.
Another important rendering rule: render-phase code should stay pure. React may render a subtree more than once before committing it, especially in development or concurrent scenarios. If I put side effects in the render body, I create duplicate or out-of-order behavior very quickly.
Trade-offs
- React’s model is powerful, but it rewards teams that understand identity and composition.
- Local component state keeps code simple, but it becomes fragile when components mount and unmount frequently.
- Keys solve identity problems, but only if they are truly stable and data-derived.
- Over-abstracting early can hide the render model instead of making it easier to use.