React reconciliation determines how component state persistence works during re-renders. Learn how React Fiber maintains your UI state across cycles.
I remember the first time I built a form in React that kept wiping its own input fields whenever the parent component re-rendered. I spent about three hours digging through documentation, convinced I’d found a bug in the library itself. It turned out I was just breaking the component's stable identity.
If you’ve ever felt like your state is "disappearing" for no reason, you’re likely fighting against how React manages the tree. Understanding how React reconciliation works isn't just an academic exercise; it's the difference between a smooth user experience and a buggy, flickering interface.
When you write JSX, you're describing a tree. React doesn't just render this tree once; it constantly compares the new tree you provide against the one currently in the browser. This process, often powered by the React Fiber architecture, is how React decides what to keep and what to destroy.
Think of React as a very diligent, slightly literal-minded librarian. When a component re-renders, React walks the tree. If it sees the same component type at the same position in the tree, it assumes it's the same instance. It keeps the state, the hooks, and the DOM nodes. If the position changes or the component type switches, it tears everything down and starts from scratch.
This is the core of component state persistence. If you define a component inside another component’s render function, you are effectively telling React: "This is a brand new component every single time."
JSXfunction Parent() { // DON'T DO THIS function Child() { const [count, setCount] = useState(0); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; } return <Child />; }
In the code above, every time Parent re-renders, Child is redefined. React sees a different function reference, assumes the tree has changed, destroys the old Child, and mounts a new one. Your count state? It’s gone. You’ve broken the stable identity.
We often talk about the virtual DOM as a performance optimization, but its real job is maintaining the relationship between your code and the browser's reality. When React keys and reconciliation: Why stable identity matters, we are essentially giving React a way to track identity even when the order of elements changes.
Without keys, or by using unstable ones like array indices, React gets confused. It tries to guess if an item moved or was replaced. If it guesses wrong, you get UI bugs—like a checkbox that stays checked when you filter a list, or an input field that keeps focus on the wrong element.
I once worked on a dashboard where we were using Math.random() for keys. It was a disaster. Around 280ms of latency was added to every update because React was essentially throwing away the entire DOM tree and rebuilding it from scratch every time the state updated. It was a heavy, unnecessary cost.
If you want to preserve state during a render cycle, follow these three rules:
key prop explicitly. Changing the key tells React: "This is officially a new instance, please wipe the old state."This is also a major factor when working with Next.js Server Components hydration. When the server sends HTML and the client tries to hydrate it, if the tree structure doesn't match perfectly, React will often dump the server-rendered content and re-render everything to be safe. It’s a silent performance killer.
Sometimes, we over-engineer this. I’ve seen developers spend days wrapping components in useMemo or memo to prevent re-renders, only to realize the re-render wasn't the performance bottleneck—it was the reconciliation logic itself.
Before you start memoizing everything, ask yourself: is the component's identity stable? If the parent re-renders, does the child stay in the same place in the tree?
I’m still not 100% sure if the upcoming React compiler will fully eliminate the need for us to worry about these manual identity patterns. My guess? It will handle the boring stuff, but understanding the underlying tree structure will remain a superpower for when things inevitably get complex. Don't worry about being perfect; just focus on keeping your references stable and your keys unique.
Does changing a component's key always destroy state?
Yes. When the key prop changes, React treats it as a completely different component instance and triggers a full unmount and remount.
Why does using an index as a key cause issues? If you reorder the list, the index of an item changes. React looks at the new tree, sees an item at index 0, and assumes it’s the same one that was at index 0 before, even if the data inside has changed. This leads to state mismatches.
Is it okay to define helper functions inside a component? Yes, that's fine. The problem is specifically defining components (functions that return JSX) inside other components.
Next.js hydration errors happen when the server and client disagree. Learn how to debug React hydration mismatch and sync your components for faster apps.