You write:
const [count, setCount] = useState(0);
Then the user clicks a button:
setCount(count + 1);
And suddenly:
Count: 0
becomes:
Count: 1
It looks simple.
But what actually happens between calling setCount() and seeing 1 on the screen?
React doesn't immediately find the <h1> element and change its text.
Instead, the state update starts a series of steps that eventually lead to the browser displaying the new UI.
The simplified mental model is:
State Update → Render → Reconciliation → Commit → Browser
Let's follow that process from beginning to end.
The Example
We'll use a simple counter:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Initially, count is 0.
The user sees:
Count: 0
[ Increment ]
When the button is clicked, this runs:
setCount(count + 1);
So the state needs to change:
0 → 1
What happens next?
1. The State Update Is Scheduled
The first thing to understand is that setCount() is not a DOM manipulation command.
When you write:
setCount(1);
you're not telling the browser:
"Find the
<h1>and change its text to 1."
You're telling React that the component's state has changed and that its rendered output may need to change.
That's a fundamentally different approach from imperative DOM manipulation.
For example, this is imperative:
document.querySelector("h1").textContent = "Count: 1";
Here, you're directly telling the browser what to change.
With React:
setCount(1);
you describe a state change, and React takes responsibility for determining the UI that should result from it.
2. React Renders the Component
After processing the state update, React needs to determine what the UI should look like with the new state.
Conceptually, our component now renders with:
count = 1
So this:
<h1>Count: {count}</h1>
produces:
<h1>Count: 1</h1>
This is what we mean when we say the component re-renders.
And here's one of the most important React concepts:
A re-render does not mean the entire DOM is recreated.
React is calculating the component's next UI output. It isn't throwing away the existing browser DOM and rebuilding everything from scratch.
So:
Re-render ≠ DOM rebuild
3. React Determines What Changed
Now React has a previous UI representation and a new one.
Previously:
Count: 0
Now:
Count: 1
The surrounding structure hasn't changed.
The <div> is still there.
The <h1> is still there.
The <button> is still there.
Only the displayed text has changed.
React's reconciliation process determines how the new element tree relates to the previous one and what work needs to be performed.
This is why reconciliation is often described as the process of figuring out what needs to change.
It's important not to think of this as React taking two snapshots of the browser DOM and comparing them.
React works with its own internal representations and reconciliation machinery to determine how the current tree should transition toward the next one.
4. React Commits the Changes
Once React has determined the work that needs to happen, it enters the commit phase.
This is where the required changes are applied to the host environment.
In a browser, that means updating the DOM.
For our counter, the result is effectively:
<h1>Count: 1</h1>
The important thing is that React doesn't need to recreate the entire DOM structure just because the state changed.
The existing DOM can be updated where necessary.
So we can think of the process as:
State Update
↓
Render
↓
Reconciliation
↓
Commit
↓
DOM Update
That's the core React mental model.
5. Then the Browser Has to Render It
React updating the DOM isn't quite the same thing as the user seeing pixels change.
The browser still has its own rendering work to perform.
A simplified version looks like:
DOM + CSS
↓
Style
↓
Layout
↓
Paint
↓
Composite
↓
Pixels
So there are actually two different systems involved.
React determines and applies the UI changes.
The browser takes the resulting DOM, styles, and other rendering information and turns them into pixels.
This distinction becomes especially useful when you start learning about frontend performance.
6. Render Doesn't Always Mean DOM Changes
Here's another important misconception.
Suppose a component renders again.
That doesn't necessarily mean the browser DOM changes.
Imagine React renders the same output as before:
Previous:
<h1>Hello</h1>
Next:
<h1>Hello</h1>
There may be no meaningful DOM mutation required.
So these are three different things:
Render
Reconciliation
DOM Update
A render can happen without producing a DOM change.
That's why saying:
"The component rendered, so React changed the DOM."
isn't necessarily correct.
7. Why Does a Component Re-render?
State updates are one reason.
But they're not the only reason a component can render.
For example, a component may render because:
- its state changed
- its parent rendered
- a context value it uses changed
- an external store triggered an update
- another React-driven update occurred
This is why the statement:
"React only re-renders when state changes."
is too simplistic.
The more useful question is:
What caused this component to render, and what work did that render actually perform?
That question becomes very important when debugging performance.
8. What About the Virtual DOM?
You've probably heard:
"React uses a Virtual DOM."
And you've probably also heard:
"The Virtual DOM is a copy of the real DOM."
That's a useful beginner explanation, but it's an oversimplification.
A better mental model is:
React maintains in-memory representations of the UI and uses them as part of its rendering and reconciliation process.
When state changes, React calculates the next UI and determines how it should transition from the current tree.
The important idea isn't:
Virtual DOM = faster DOM
The more useful idea is:
You describe the UI.
React manages the transition.
This is a major part of what makes React declarative.
9. Declarative vs Imperative
Consider an imperative approach.
You might manually update the DOM:
title.textContent = user.name;
if (user.isOnline) {
status.textContent = "Online";
status.classList.add("active");
} else {
status.textContent = "Offline";
status.classList.remove("active");
}
Now your code needs to keep track of:
- which element changed
- what value it should have
- which classes need to be added or removed
- what happens when multiple pieces of state change
React lets you describe the desired UI instead:
<p>
{isOnline ? "Online" : "Offline"}
</p>
You describe the UI for the current state.
React handles the transition from the previous result to the next one.
That's the declarative model.
10. What Happens With Multiple State Updates?
Consider:
setName("Nayan");
setAge(25);
setLoading(false);
It would be misleading to assume that React necessarily does this:
setName()
↓
render
setAge()
↓
render
setLoading()
↓
render
React can batch state updates so that multiple updates are processed together when appropriate.
Conceptually:
Multiple Updates
↓
React processes them
↓
Render
↓
Reconciliation
↓
Commit
This can reduce unnecessary work.
The exact behavior depends on the React version and execution context, but the important mental model is:
State setters are requests for React to update its rendered output, not direct DOM commands.
11. Does React Rebuild Everything?
No.
This is probably the most important misconception to eliminate.
Suppose a component contains:
return (
<div>
<h1>Welcome</h1>
<p>{isOnline ? "Online" : "Offline"}</p>
<button>Change Status</button>
</div>
);
If isOnline changes, the component may render again.
But that doesn't mean React throws away:
<div>
<h1>
<button>
and creates completely new DOM elements for everything.
React's reconciliation process determines what changed and the commit phase applies the necessary updates.
So keep these distinctions in mind:
Component re-render
≠
Entire DOM recreated
Render
≠
DOM update
State update
≠
Direct DOM manipulation
These distinctions will save you from a lot of confusion later.
12. Why Can Re-renders Still Be Expensive?
If React doesn't rebuild the entire DOM, does that mean re-renders are free?
Definitely not.
A render can still perform a lot of JavaScript work.
Imagine a component that:
- renders thousands of elements
- performs expensive calculations
- processes a large dataset
- creates many objects
- triggers many child renders
Even if the final DOM change is tiny, the work required to calculate that result might be expensive.
That's why React performance optimization focuses on questions such as:
- Where should state live?
- Which components actually need to render?
- Are expensive calculations being repeated?
- Are unnecessary child renders happening?
- Would
React.memohelp? - Would
useMemooruseCallbackactually solve a real problem? - Should a large list be virtualized?
The goal isn't:
"Prevent every re-render."
The goal is:
Avoid unnecessary expensive work.
13. The Complete Journey
Let's return to our original example:
setCount(count + 1);
The user doesn't directly cause this:
setCount()
↓
DOM changes
A better mental model is:
User interaction
↓
State update
↓
React renders
↓
Reconciliation
↓
Commit
↓
Browser rendering
↓
Pixels on screen
For our counter, the visible result is simply:
Count: 0
↓
Count: 1
But underneath that tiny change, React and the browser perform several steps to produce the final result.
The Mental Model You Should Remember
When you see:
setCount(count + 1);
don't think:
"React changes the DOM."
Think:
"The state changed, so React needs to determine what the UI should look like now."
That leads to this mental model:
State changes
↓
React calculates the next UI
↓
React reconciles it
↓
React commits required changes
↓
Browser renders the result
And remember these three rules:
1. State updates are not DOM commands
setState ≠ DOM manipulation
2. Re-rendering does not mean rebuilding the entire DOM
Re-render ≠ DOM rebuild
3. Rendering does not necessarily mean the DOM changes
Render ≠ DOM update
Final Takeaway
React's core idea is surprisingly simple:
You describe what the UI should look like for a given state, and React manages the transition from the previous UI to the next one.
So the next time you write:
setCount(count + 1);
remember that you're not telling the browser:
"Change this text to 1."
You're changing the state that drives your UI.
React then determines the next rendered result, reconciles it with the previous one, commits the necessary changes, and the browser eventually turns that result into pixels.
Top comments (0)