AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 04 — Rendering and Reconciliation — How React Turns JSX Into DOM

04 — Rendering and Reconciliation — How React Turns JSX Into DOM

August 13, 20266 min read
Download as Markdown

"Magic makes the screen update" was my render-pipeline model, which made every performance mystery a dead end. The machinery that finally clicked: React's update cycle has two phases — render (compute what the UI should look like) and commit (touch the real DOM), separated by a virtual DOM diff. [1][2] The virtual DOM isn't a performance feature by itself; it's a staging area that lets React compute the minimum set of DOM mutations before committing them.

React is declarative

The starting point is that React follows a declarative approach: I describe what the UI should look like for a given state, and React takes care of turning that description into DOM updates [1]. I never write document.createElement or el.appendChild myself. Instead:

  • I write components that return JSX based on their props and state.
  • When state changes, React calls the components again to get a new description.
  • React reconciles the new description with the previous one and applies the diff.

The contrast is with an imperative approach, where I'd manually find elements and mutate them. Declarative code is easier to reason about because the UI is a pure function of state — given the same state, the same UI comes out. The cost is the machinery that figures out the diff.

The virtual DOM and reconciliation

The virtual DOM (VDOM) is a lightweight in-memory description of the UI — plain JavaScript objects representing elements [1]. React keeps two versions: the current tree and the one from the last commit. The process that compares them is called reconciliation [1][2]:

  1. Render phase. React calls the components (and their children) and builds a new VDOM tree. This phase is pure — no DOM access, no side effects, just computing the description. React can pause, abort, or restart it.
  2. Diff. React compares the new VDOM against the previous one, element by element, to determine what changed.
  3. Commit phase. React applies the calculated changes to the real DOM in one batch — inserting, updating, or removing nodes.

The diff is the clever part. It's not a generic tree-diff (that's expensive); it uses heuristics — a few shortcut rules instead of a full comparison. Two rules drive it:

  • Different element types → tear down and rebuild. If <a> becomes <div>, or <Counter /> becomes <Timer />, React destroys the old subtree and builds a new one. State is reset.
  • Same type → keep the instance, update props. If <div className="a"> becomes <div className="b">, React updates just the changed attributes. State and component instances survive.

That second rule is exactly why the key prop matters in lists (see the notes on components) — it's how React matches repeated elements across renders when their order might change.

1 · Render (pure) new VDOM <App> <Header/> <List> <Row/> <Row/> </List> no DOM · pure 2 · Reconcile (diff) compare trees same type? → update props diff type? → rebuild subtree list? → match by key computes min mutations 3 · Commit (real DOM) apply changes insert / update / remove nodes then run effects new tree mutations render is interruptible · commit is synchronous and touches the DOM once

Why this matters for performance

The two-phase split is the lens I use for every "why is this slow" question [3]:

  • A slow render means a component is doing expensive work during the render phase, and that work runs on every render even when nothing relevant changed. The fix is to do less work in render (memoize with useMemo/useCallback, move expensive computation out, or React.memo the component so it skips re-rendering when props are unchanged).
  • A slow commit means React is writing too many DOM nodes. The fix is usually to render fewer nodes, or to keep list items stable with good keys so React doesn't tear down and rebuild large subtrees.

Kent C. Dodds's framing stuck with me: fix the slow render before you fix the re-render. [3] Re-renders are often cheap; a re-render only matters if the render itself is expensive. Memoizing a component that renders in microseconds solves nothing.

How I use this

When the UI feels sluggish, I first ask whether the problem is in the render phase or the commit phase. A React DevTools profiler render that takes tens of milliseconds points at the render work itself — that's where I look for expensive computation, missing memoization, or a parent re-rendering children that didn't need to. DOM thrash (lots of insertions) shows up as commit time. The mental separation keeps me from reaching for useMemo reflexively: I only memoize when a profiler run shows an actual cost, because most renders are cheap and premature memoization adds bookkeeping overhead for no gain.

References

[1] React team, "Render and commit," react.dev, 2024. [Online]. Available: https://react.dev/learn/render-and-commit

[2] ui.dev, "Rendering in React," 2023. [Online]. Available: https://ui.dev/why-react-renders

[3] K. C. Dodds, "Fix the slow render before you fix the re-render," kentcdodds.com, 2021. [Online]. Available: https://kentcdodds.com/blog/fix-the-slow-render-before-you-fix-the-re-render

[4] React team, "Preserving and resetting state," react.dev, 2024. [Online]. Available: https://react.dev/learn/preserving-and-resetting-state

Knowledge check · Question 1 of 5

React's update cycle is best described as two phases:

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!