How React Rendering Works

Reviewed & published by Brayan K

Rendering is the process where React calls your component to build a virtual DOM description, diffs it against the previous one (reconciliation), and commits only the differences to the real DOM. A re-render runs your function again, but it does not automatically touch the page — only changed nodes are updated.

Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1️⃣ Render Phase vs Commit Phase

Rendering happens in two phases. In the render phase, React calls your component to build a new virtual-DOM tree — this must be pure and has no DOM side effects. In the commit phase, React applies the diff to the real DOM and runs effects. The render is cheap; the commit is where the costly DOM work happens, and React keeps it minimal.

function Counter({ count }) {
  // RENDER phase: just return a description. No DOM here.
  return <p>Count: {count}</p>;
}
// COMMIT phase (React does this): updates only the text node
// that changed from "Count: 0" to "Count: 1".
// RENDER = build a description (cheap). COMMIT = touch the DOM (costly).
// React only commits the DIFFERENCES between the old and new description.
function render(state) {
  return { text: "Count: " + state.count };   // a virtual-DOM "snapshot"
}

const before = render({ count: 0 });
const after  = render({ count: 1 });

console.log(before.text);                       // Count: 0
console.log(after.text);                        // Count: 1
console.log(before.text === after.text);        // false -> COMMIT this change

2️⃣ What Triggers a Re-render & the Diff

A component re-renders when its state changes (via a setter), when its props change, or when its parent re-renders. React then runs reconciliation: it compares the new virtual DOM to the old one node by node and computes the smallest set of real changes. Inside lists, stable keys let React match items across renders instead of rebuilding them.

function List({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.text}</li>    
      ))}
    </ul>
  );
}
// On re-render React diffs by key: unchanged <li>s are reused,
// only added/removed/changed ones are committed.
// DIFFING: compare two snapshots, return only what changed.
function diff(prev, next) {
  const changes = [];
  for (const key in next) {
    if (prev[key] !== next[key]) changes.push(key + ": " + prev[key] + " -> " + next[key]);
  }
  return changes;
}

const prev = { title: "Hi", count: 0, color: "red" };
const next = { title: "Hi", count: 1, color: "red" };

console.log(diff(prev, next));   // [ "count: 0 -> 1" ]  <- only count is committed
console.log(diff(prev, prev));   // []  <- nothing changed, DOM untouched

Your turn — decide commit vs skip:

// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// A re-render produces a NEW snapshot, but React BAILS OUT when the
// new value equals the old one (Object.is comparison).
// 1) Compare with === to decide if a commit is needed.
// 2) Return "commit" or "skip".

function willCommit(prevValue, nextValue) {
  return prevValue ___ nextValue ? "skip" : ___;  // 👉 ===   and   "commit"
}

console.log(willCommit(5, 5));      // skip
console.log(willCommit(5, 6));      // commit
console.log(willCommit("a", "a"));  // skip

// ✅ Expected output:
//    skip
//    commit
//    skip

3️⃣ Re-render ≠ DOM Update, and Bailing Out

A re-render is just React re-running your function and re-diffing — it is usually cheap. If the produced tree matches the previous one, no DOM is touched. React also bails out: if you set state to a value equal to the current one (by Object.is), it can skip the work entirely. For expensive subtrees, React.memo, useMemo, and useCallback help skip re-renders — but profile before optimizing.

// Bail-out: setting the SAME value skips the re-render.
function handleClick() {
  setCount(c => c);   // returns the same value -> React may bail out
}

// Skip re-rendering an expensive child whose props did not change:
const ExpensiveList = React.memo(function ExpensiveList({ items }) {
  return <List items={items} />;
});

🧩 Reorder Challenge

These steps describe React's pipeline from a state change to the screen. Put them in the right order:

A.   Diff the new tree against the previous one (reconcile)
B.   A state setter is called, scheduling an update
C.   Commit only the differences to the real DOM
D.   Run your component to build a new virtual DOM
E.   Run effects (useEffect) after paint

Common Errors (and the fix)

📋 Quick Reference

TermMeaning
Render phaseBuild a new virtual DOM (pure, no DOM writes)
ReconciliationDiff new tree vs old, find minimal changes
Commit phaseApply changes to the real DOM, run effects
Triggersstate set, props change, parent re-render
Bail outSkip work when nothing changed

🧠 Quick Recall

1. Name the three things that trigger a re-render.

A state update, a change in received props, or the parent re-rendering.

2. Does every re-render update the DOM?

No. Only the diff is committed; if the new tree matches the old one, the DOM is untouched.

3. What do keys help React do?

Match list items across renders during reconciliation so it reuses unchanged ones instead of rebuilding.

Mini-Challenge: Count the Commits

A series of state updates arrives over time. A commit happens only when the new value differs from the last committed one. Count the commits and check your output.

// 🎯 MINI-CHALLENGE: count how many re-renders actually touch the DOM
//
// You are given a list of state updates. A render "commits" only when the
// new count differs from the previous one.
// 1. Walk the updates, tracking the previous committed count.
// 2. Count how many updates cause a real DOM commit.
// 3. console.log the number of commits.
//
// ✅ Expected output:
//    3

const updates = [0, 0, 1, 1, 2, 5, 5];  // setState calls over time

// your code here

🎉 Lesson Complete

Practice quiz

What are React's two rendering phases?

  • Mount and unmount
  • Fetch and paint
  • The render phase (build virtual DOM) and the commit phase (apply to real DOM)
  • Diff and reconcile

Answer: The render phase (build virtual DOM) and the commit phase (apply to real DOM). Render builds the virtual DOM (pure, no DOM writes); commit applies the diff and runs effects.

Does every re-render update the real DOM?

  • No — only the diffed changes are committed; an unchanged tree leaves the DOM untouched
  • Yes, always
  • Only on the first render
  • Only if you call useEffect

Answer: No — only the diffed changes are committed; an unchanged tree leaves the DOM untouched. A re-render produces a new virtual DOM; React commits only the differences.

Which three things trigger a re-render?

  • A mutated variable, a console.log, a comment
  • Only state updates
  • Network requests, timers, and clicks
  • A state update, a props change, or the parent re-rendering

Answer: A state update, a props change, or the parent re-rendering. Re-renders come from a state setter, changed props, or a parent re-rendering — not plain variable changes.

What is reconciliation?

  • Fetching data before render
  • Diffing the new virtual DOM against the previous one to find minimal changes
  • Running effects after paint
  • Resetting component state

Answer: Diffing the new virtual DOM against the previous one to find minimal changes. Reconciliation is the diff step that computes the smallest set of real DOM changes.

What do keys help React do during reconciliation?

  • Match list items across renders so unchanged ones are reused
  • Style list items
  • Sort the list
  • Prevent re-renders entirely

Answer: Match list items across renders so unchanged ones are reused. Stable keys let React match items across renders instead of rebuilding them.

In the render phase, your component must be…

  • Allowed to write to the DOM
  • Async
  • Pure, with no DOM side effects
  • Wrapped in useEffect

Answer: Pure, with no DOM side effects. The render phase must be pure; side effects belong in the commit phase (e.g. useEffect).

What does 'bailing out' mean in React?

  • Throwing an error
  • Skipping work when nothing changed (e.g. setting state to an Object.is-equal value)
  • Unmounting a component
  • Forcing a re-render

Answer: Skipping work when nothing changed (e.g. setting state to an Object.is-equal value). If a state update equals the current value, React can skip the re-render work entirely.

Why might 'my change doesn't show up' happen?

  • You called too many setters
  • You used a key
  • You wrapped it in memo
  • You mutated a plain variable instead of calling a state setter

Answer: You mutated a plain variable instead of calling a state setter. React only re-renders on state/props changes; mutating a variable doesn't notify React.

Which comparison does React use to decide a bail-out on equal state?

  • JSON.stringify
  • Object.is
  • deep equality
  • ==

Answer: Object.is. React uses Object.is to compare the new state value with the current one.

Using the array index as a list key can cause…

  • Faster rendering
  • A compile error
  • Items to jump or lose input state when the list changes
  • Automatic memoization

Answer: Items to jump or lose input state when the list changes. Indexes shift on add/delete/reorder, so React matches the wrong items; use a stable, unique id.

Continue this course

Frequently asked questions

Does a re-render always update the DOM?

No. A re-render runs your component function and produces a new virtual DOM. React diffs it against the previous one; only the differences are committed to the real DOM. If nothing changed, the DOM is untouched.

What triggers a re-render?

Three things: a state update via a setter, a change in the props a component receives, or its parent re-rendering. Changing a plain variable does not — React must know via state.

What is reconciliation?

Reconciliation is the diffing step where React compares the new virtual DOM tree to the previous one, decides the minimal set of changes, and applies them. Keys help it match list items correctly.

How can I stop unnecessary re-renders?

Bail out with React.memo for components, useMemo/useCallback for derived values and functions, and stable keys for lists. But measure first — most re-renders are cheap.

Related lessons