The Rendering Lifecycle
React is often described as a library that “updates the UI when state changes.” But how exactly does it do that efficiently? Understanding the rendering lifecycle—specifically the distinction between the Render Phase and the Commit Phase—is the key to optimizing performance and avoiding common pitfalls like infinite loops or unnecessary re-renders.
First Principles: The Physics of Rendering
Why does React exist? Why not just update the DOM directly?
The Cost of DOM Mutation
The browser’s DOM is slow to update because every change triggers a cascade of calculations:
- Recalculate Styles: The browser figures out which CSS rules apply.
- Reflow (Layout): The browser calculates the position and geometry of every element.
- Repaint: The browser paints pixels to the screen.
If you update the DOM 100 times in a loop, the browser might try to reflow 100 times. React solves this by batching updates and using a Virtual DOM.
The Virtual DOM (VDOM)
The Virtual DOM is a pure JavaScript object tree that mirrors the actual DOM.
- Accessing JS Objects: ~0.0001ms
- Accessing Real DOM: ~0.1ms (1000x slower)
React calculates changes in the cheap VDOM world and then applies the minimal set of changes to the real DOM in one go.
Phase 1: The Render Phase
The Render Phase is where React determines what changes need to be made.
- Trigger: A state update, parent re-render, or context change schedules a render.
- Calculation: React calls your component function.
- Reconciliation: React compares the new Virtual DOM tree returned by your component with the previous one.
Fiber Architecture
Since React 16, this phase is powered by Fiber. Fiber allows React to:
- Pause, abort, or prioritize work.
- Split rendering work into chunks (time-slicing).
- Prioritize user interactions (clicks/input) over background data fetching.
Phase 2: The Commit Phase
The Commit Phase is where the side effects happen. This phase is synchronous and cannot be interrupted.
- Apply Changes: React applies the diffs calculated in the Render Phase to the real DOM.
- Layout Effects:
useLayoutEffectruns synchronously after DOM mutations but before the browser paints. - Paint: The browser paints the new UI to the screen.
-
Passive Effects:
useEffectruns asynchronously after the paint.
Rendering Lifecycle
[!NOTE] This module explores the core principles of Rendering Lifecycle, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.
1. Interactive: Render Cycle Visualizer
Explore how React moves through the phases when state changes. Click “Trigger Update” to start the cycle.
Component State
Current Status
2. Batching
React 18 introduced Automatic Batching. This means that multiple state updates occurring inside event handlers, promises, or timeouts are grouped into a single re-render to improve performance.
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React 18: Only ONE re-render happens here!
}
Prior to React 18, updates inside setTimeout or promises were not batched, causing two renders. Now, they are safe.
3. Optimizing the Render Cycle
Since rendering happens often, it must be fast. If a component is slow to render, it blocks the main thread.
React.memo
You can wrap a component in React.memo to skip re-rendering if its props haven’t changed.
const ExpensiveComponent = React.memo(function({ data }) {
// Only re-renders if `data` prop changes (referential equality)
return <div>{slowCalculation(data)}</div>;
});
React.memo adds comparison overhead. Only use it when you've identified a rendering bottleneck.
4. Key Takeaways
- Render ≠ Update: Rendering is just calculating the diff.
- Virtual DOM: The lightweight copy used for diffing.
- Commit: When the DOM is actually touched.
- Side Effects: Belong in
useEffect, which runs after the paint.