React rendering: Tracing Prop Changes from Update to DOM Patch
React rendering pipeline demystified: Learn how a prop change triggers reconciliation and DOM patching to keep your UI in sync with your component state.
When I started as a junior dev, I treated the UI as a black box—data went in, and pixels appeared on the screen. It wasn't until I started debugging a massive performance lag on an interactive dashboard that I realized I needed a better grasp of the underlying engine.
Understanding react rendering isn't about memorizing the docs; it's about visualizing the journey of your data. When a parent component updates, your app doesn't just "refresh." It follows a specific, predictable path to ensure the DOM only changes where it absolutely must.
The Trigger: Why the Pipeline Starts
Everything starts with a trigger. In React, this is usually a state change, a context update, or a parent passing new props down. Let's say you have a UserCard component receiving a name prop. When the parent calls setName('Mahamudul'), the React Fiber engine wakes up.
It’s important to remember that React doesn't touch the DOM yet. Instead, it starts the "Render Phase." This is pure JavaScript execution—React calls your component function to see what the UI should look like. If you've ever struggled with why your variables reset, check out my guide on React useRef and Component Memory: Why Variables Reset on Re-render to see how this phase interacts with memory.
Reconciliation and the Fiber Tree
Once React has the output of your component (the new Virtual DOM node), it needs to compare this to what was there before. This is where next.js reconciliation (or rather, the React reconciliation engine running within Next.js) shines.
React traverses the Fiber tree. It compares the "current" tree with the "work-in-progress" tree. It’s looking for three things:
- Did the component type change? (e.g.,
<div>to<span>) - Did the keys change?
- Did the props change?
If nothing changed, React skips the sub-tree entirely. This is why stable keys are vital. If you’re struggling with list performance, you’re likely hitting issues with how React tracks these identities during the React rendering lifecycle.
The Commit Phase: DOM Patching
After the reconciliation is done, React enters the "Commit Phase." This is the only time React talks to the real browser DOM.
Think of this as a list of instructions:
- "Update the
textContentof this specific element." - "Remove this node."
- "Append this new node."
DOM patching is efficient because it’s surgical. Instead of re-rendering the whole page (which would take roughly 200ms of layout thrashing), React applies only the specific changes needed. I once spent about two days debugging a flicker issue, only to realize I was using an unstable object reference in my dependency array, causing unnecessary reconciliation cycles.
A Practical Example
Consider this simple structure:
JAVASCRIPTfunction Parent() { const [count, setCount] = useState(0); return <Child label={count} />; } function Child({ label }) { return <div>Count: {label}</div>; }
When setCount is called:
- Render Phase:
Parentre-renders, generating a newChildelement withlabel={1}. - Reconciliation: React sees
Childis the same type as before. It checks props.labelhas changed from0to1. - Commit Phase: React updates the text node inside the
divto1.
It’s a tight loop. If you want to dive deeper into how this works across different hooks, useState and useEffect: A Mental Model for React Beginners explains how these pieces fit into the state-batching model.
Common Pitfalls
You might be tempted to force an update or use useLayoutEffect to "fix" something. Don't. Most "rendering issues" are actually architectural issues where state is living in the wrong place. If you're moving state up or down, keep in mind that the react rendering process will always re-evaluate the component tree below the point of change.
I still find myself reaching for console.log during the render phase to trace cycles. It’s not the most sophisticated tool, but seeing exactly when a component function runs is the best way to demystify these internals. Don't stress if you don't get it right away; the mental model clicks only after you've traced a few bugs in production.
What’s the one thing I’d do differently? I’d stop trying to optimize prematurely. Trust the reconciliation engine to do its job unless you have a measurable performance bottleneck. Most of the time, the default behavior is faster than any "clever" manual DOM manipulation you might try to inject.