The render cycle — why components re-render
"Render" doesn't mean "update the screen" — it means "call the component function and see what it returns." Whether the real DOM actually changes afterward is a separate, later step React decides on its own. Confusing the two is where most React performance confusion starts.
4 min read
Three things trigger a re-render, and only three
A component re-renders — meaning React calls its function again — for exactly three reasons: its own state changed (a setState call), its props changed (its parent passed different values this time), or its parent re-rendered at all, regardless of whether the props it received actually changed. That third one surprises people the most: by default, a child re-renders whenever its parent does, even with completely identical props — this is the specific behavior React.memo (covered in the performance lesson later in this domain) exists to opt out of, when it's actually worth the trade-off.
Rendering is not the same thing as updating the DOM
function Clock({ time }) {
console.log("Clock rendered"); // this runs every single re-render
return <p>{time}</p>;
}"Render" specifically means: React calls Clock(props), gets back a JSX description, and that's it — nothing about the real DOM has necessarily changed yet. React then compares this new description against the previous one it kept from last time (this comparison step is reconciliation), and only after that comparison does it touch the real DOM at all — and only the specific parts that actually differ. A component can re-render (its function runs, console.log fires) while producing an output identical to before, in which case React touches zero real DOM nodes — the render happened, the DOM update didn't, because there was nothing to update.
The two-phase process: render, then commit
Render phase - React calls component functions, builds a description
of what the UI should look like (this can happen without
touching the real DOM at all, and can even be thrown away)
Commit phase - React applies the actual, minimal set of DOM changes
needed to match the new description — this is the only
phase that touches real, visible pixels on screen
React deliberately separates figuring out what changed (the render phase — pure computation, no visible side effects) from actually applying those changes (the commit phase — the only point real DOM mutations happen). This separation is what makes features like React's concurrent rendering possible at all — React can start a render, pause it, even discard it entirely without ever having touched the visible page, precisely because the render phase itself has no observable effect on what a user sees until commit actually happens.
Why re-rendering a parent re-renders every child by default
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ExpensiveChart /> {/* re-renders too, even though nothing it needs changed */}
</div>
);
}When App re-renders because count changed, React by default re-renders every component App renders as JSX — including ExpensiveChart, which has no props depending on count at all. This is intentional, not an oversight: re-rendering is comparatively cheap (it's the commit/actual-DOM-mutation step that's expensive, and reconciliation specifically avoids unnecessary DOM writes even when a re-render happens), and defaulting to "re-render everything, let reconciliation figure out what actually needs a DOM update" is simpler and correct far more often than it's a real performance problem. Reaching for React.memo before actually measuring a genuine performance issue is a common, premature optimization — the render-cycle default exists precisely because it's usually fine.
Reading a component's re-render count directly, for real intuition
function Component({ value }) {
const renderCount = useRef(0);
renderCount.current += 1;
console.log(`Rendered ${renderCount.current} times`);
return <p>{value}</p>;
}(useRef, covered properly in its own place later in this domain, is a value that survives across re-renders without causing one when it changes — exactly what's needed here to count renders without the counting itself triggering more renders.) Dropping a log line like this into a real component and watching the console while interacting with the app is one of the fastest ways to build real, hands-on intuition for exactly when and how often re-renders actually happen — often revealing that a component re-renders far more often than expected, for reasons that only make sense once the "parent re-renders → children re-render by default" rule above is understood directly.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What are the three things that can trigger a component to re-render?
2. Is 'rendering' the same thing as 'updating the real DOM'?
3. Why does React re-render a component's children by default whenever the parent re-renders, even without prop changes?