The 2 Stages of React Rendering: Render Phase vs. Commit Phase

Chris is scratching his head while debugging some React code.
"The console.log definitely printed, so why is the screen exactly the same? And why is the console logging everything twice?"
Many developers confuse the word "Rendering" with "Painting" (drawing to the screen). However, in the world of React, rendering does not strictly mean updating the screen. In fact, a render can occur without a single pixel changing on the screen.
Accurately understanding the process from when React calls a component to when it reflects changes on the screen is key to preventing unnecessary re-renders and predicting exactly when useEffect will fire. Today, we dig into the secrets of the Render Phase and the Commit Phase.
React's Order System
Let's compare React's rendering process to a restaurant order system.
The crucial point is this: Just because an order ticket was written (Render Phase) does not guarantee that food will come out (Commit Phase). If the order content is exactly the same as before, the Chef doesn't start cooking.
1. Render Phase
This is the domain of "Calculation." React calls the component function, takes the returned JSX (Virtual DOM), and compares it with the result of the previous render. This process is called Reconciliation.
// Render Phase Example
function UserProfile({ name }: { name: string }) {
// 1. Component Body Execution (Render Phase)
console.log('Render Phase: Calculating...');
// 2. Returns JSX (This object is compared to the previous one)
return <div>{name}</div>;
}Why does the console log twice? (Strict Mode)
The reason console.log appears twice in development mode is due to React's Strict Mode. React intentionally calls the component twice to verify: "Is the Render Phase pure?" If the results differ between the two executions, it signals that there are side effects that need fixing.
2. Commit Phase
This is the domain of "Application." The "changes" calculated during the Render Phase are applied to the actual DOM.
// Runs after Commit Phase
useEffect(() => {
console.log('After Commit Phase: Now executing side effects');
}, []);The Complete Flow
Let's trace the flow when Chris clicks a button to change state.
What if we update the state with the same value, setCount(0)?
React compares them in the Render Phase and finds no changes. Consequently, React skips the Commit Phase. This saves the cost of DOM manipulation.
Practical Code: Verifying Execution Order
If you run the code below, in what order will the console logs appear?
import React, { useState, useEffect, useLayoutEffect } from 'react';
function CycleTest() {
console.log('1. Render Phase');
const [count, setCount] = useState(0);
useLayoutEffect(() => {
console.log('2. Commit Phase (Right after DOM update, Before Paint)');
}, [count]);
useEffect(() => {
console.log('3. Commit Phase (After Paint)');
}, [count]);
return (
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
);
}The result is 1 -> 2 -> 3.
Specifically, useLayoutEffect runs before the browser paints the screen, so it is used when you need to measure or mutate the DOM layout without causing a visual flicker. (However, useEffect is sufficient for most cases.)
Key Takeaways
Now that we know the ins and outs of rendering, it's time to look at how React transitions screens: Routing.
Continuing in: "React Router and History API: How Do SPAs Switch Pages?"