How React Works
To use React effectively, it helps to have an accurate mental model of what happens between writing a component and seeing pixels on screen. React is fundamentally a library for describing UI as a function of state, then efficiently keeping the real DOM in sync with that description. This page walks through that pipeline: how components produce element trees, what the virtual DOM is, how reconciliation diffs changes, the Fiber architecture that drives it, and the two distinct phases — render and commit — that turn your code into DOM updates.
Components return element trees
A React component is just a function that returns a description of UI. You write JSX, but JSX is not a language feature — it is syntax sugar that a compiler (Babel or the SWC compiler in Vite) transforms into plain function calls.
function Greeting({ name }) {
return <h1 className="title">Hello, {name}!</h1>;
}
That JSX compiles to a call to the JSX runtime, which is conceptually equivalent to:
import { jsx as _jsx } from "react/jsx-runtime";
function Greeting({ name }) {
return _jsx("h1", { className: "title", children: ["Hello, ", name, "!"] });
}
The return value is a plain JavaScript object — a React element — that describes what you want, not the actual DOM node:
const element = <h1 className="title">Hello, Ada!</h1>;
console.log(element.type, element.props.className);
Output:
h1 title
Because components return elements that may themselves contain other components, a single render produces a tree of elements. React’s job is to render that tree and reconcile it with whatever is currently on screen.
The virtual DOM
The virtual DOM is the in-memory tree of React elements that React keeps as a lightweight representation of the UI. Reading and writing the real DOM is comparatively expensive, so React works against this cheap object tree first, computes the minimal set of changes, and only then touches the browser’s DOM.
When state changes, React builds a new virtual DOM tree by re-running the affected components. It then compares the new tree against the previous one to decide what actually needs to change in the real DOM. This comparison step is called reconciliation.
The “virtual DOM” is a mental model, not a public API. You never manipulate it directly — you describe the desired UI and let React figure out the DOM operations.
Reconciliation and diffing
Reconciliation is the algorithm React uses to diff the new element tree against the previous one. A naive tree diff is O(n³); React makes it practical (roughly O(n)) using two heuristics:
- Different element types produce different trees. If a node changes from
<div>to<span>, React tears down the old subtree and builds a new one rather than trying to match children. - Keys identify list items across renders. When rendering lists, a stable
keylets React match elements between renders so it can move, keep, or remove items instead of rebuilding them.
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
Avoid using array indices as keys for lists that can reorder or change length — doing so causes React to mismatch items, leading to lost input state and subtle bugs.
The Fiber architecture
Under the hood, the reconciler since React 16 is built on Fiber. A fiber is an internal object representing a unit of work for one element — it tracks the element’s type, props, state, and pointers to its parent, child, and sibling fibers. This linked structure lets React process the tree incrementally.
The key benefit is that Fiber makes rendering interruptible. Instead of recursing through the entire tree in one uninterruptible pass, React can render in small chunks, pause to let the browser handle high-priority work (like user input), and resume later. This is what powers concurrent features such as useTransition and automatic batching in React 18 and 19.
Render phase vs. commit phase
A React update happens in two clearly separated phases.
| Phase | What happens | Side effects allowed? | Interruptible? |
|---|---|---|---|
| Render | React calls your components, builds the new element tree, and diffs it against the old one to compute the changes | No — must be pure | Yes (with concurrent rendering) |
| Commit | React applies the computed DOM mutations, updates refs, and runs effects | Yes | No |
During the render phase, React invokes your component functions to produce elements. This phase must be pure: it can run, pause, restart, or be thrown away, so it must not mutate external state or perform side effects.
Once React knows exactly what changed, the commit phase applies those mutations to the real DOM in a single synchronous pass, then updates refs and flushes layout and passive effects. The commit phase always runs to completion.
import { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
// Render phase: this function body runs and returns elements.
// Commit phase: this effect runs after the DOM is updated.
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return <button onClick={() => setCount((c) => c + 1)}>Clicked {count} times</button>;
}
The full pipeline, end to end:
state change
|
v
[ Render phase ] (pure, interruptible)
call components ──> new virtual DOM tree
|
v
reconcile / diff against previous tree
|
v
list of DOM mutations
|
v
[ Commit phase ] (side effects, synchronous)
apply mutations ──> real DOM ──> update refs ──> run effects
|
v
browser paints
Best Practices
- Keep components pure during render: no fetching, no DOM mutation, no writing to external variables inside the function body.
- Give list items stable, unique
keyvalues derived from data IDs, never array indices for dynamic lists. - Push side effects into
useEffect(or event handlers) so they run in the commit phase, not during render. - Let React batch updates — call multiple state setters freely; React 18+ batches them into a single render.
- Don’t optimize prematurely; React’s diffing is fast. Reach for
memo,useMemo, anduseCallbackonly when profiling shows a real cost. - Treat the virtual DOM as an implementation detail — model your UI as a function of state and trust React to compute the DOM changes.