Frontend Handbook
Written

Inside React: from JSX to DOM updates

What JSX becomes, the element and fiber trees, how reconciliation diffs them and why keys matter, the render and commit phases, and urgent vs deferred updates, each with a live demo.

Transpiling left JSX as calls to jsx(). This page follows those calls into React: the objects they return, the fiber tree React keeps for them, how it works out what changed, the order in which it runs your code and touches the DOM, and how it schedules work so input stays responsive.

Every demo runs the React this site ships (19.2, its production build) and reports what it did. Where a demo reads React's internal fields, it says so: they are useful for learning and never for application code.

01

What JSX becomes: elements

jsx(type, props, key) does almost nothing: it returns a small, plain object called a React element, a description of what you want on screen. Nothing is rendered, and no component runs, until React is given that object.

  • An element is { $$typeof, type, key, ref, props }. $$typeof is the symbol react.transitional.element, which React checks so that JSON from a server can't pass itself off as an element (a defence against injection).
  • type is a string for DOM elements ("ul") and the function itself for components. Writing <Badge /> doesn't call Badge; React calls it later, when it renders.
  • Children live in props.children. The key is kept outside props (stringified), which is why a component can't read its own key.
  • Elements are immutable snapshots: every render creates new ones, and React compares them with the previous ones to decide what to change.

Try it: write JSX and see the real objects

element, as returned by React's jsx()
{
  $$typeof: Symbol(react.transitional.element),
  type: "ul",
  key: null,
  ref: null,
  props: {
    className: "menu",
    children: [
      {
        $$typeof: Symbol(react.transitional.element),
        type: "li",
        key: "Tea",
        ref: null,
        props: {
          children: [
            "Tea",
            " ",
            {
              $$typeof: Symbol(react.transitional.element),
              type: [Function Badge],
              key: null,
              ref: null,
              props: {
                label: "new"
              }
            }
          ]
        }
      },
      {
        $$typeof: Symbol(react.transitional.element),
        type: "li",
        key: "Coffee",
        ref: null,
        props: {
          children: [
            "Coffee",
            " ",
            {
              $$typeof: Symbol(react.transitional.element),
              type: [Function Badge],
              key: null,
              ref: null,
              props: {
                label: "new"
              }
            }
          ]
        }
      }
    ]
  }
}

Creating the element called Badge 0 times: a component is only a type here. React calls it later, when it renders.

The JavaScript that ran
element.jsmeasuring…
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
function Badge({ label }) {
  return _jsx("b", { className: "badge", children: label });
}
const items = ["Tea", "Coffee"];
const element = (_jsx("ul", {
  className: "menu",
  children: items.map((item) => (_jsxs("li", {
    children: [item, " ", _jsx(Badge, { label: "new" })]
  }, item)))
}));
  • Do: Think of JSX as data: you can store elements in variables, arrays and props, and pass them around.

    Avoid: Calling components as functions (Badge(props)) instead of rendering them: React no longer sees a component, so its hooks belong to the caller.

Read more:React: Your UI as a tree(opens in a new tab)React: createElement(opens in a new tab)

02

Elements, fibers and the DOM

Elements are thrown away after every render, so they can't hold state. For that React keeps a fiber for each component and DOM node: a long-lived object that records its type and key, its props, its state, and links to its parent, first child and next sibling.

  • A function component's hooks are a linked list on its fiber, in call order. That is why hooks must run in the same order every render: React finds each hook's state by its position in that list.
  • React keeps two copies of the tree. The current one matches the screen; a render builds the next version in the other copy (the work-in-progress), and committing swaps them. Copies are reused, so renders don't allocate a whole new tree.
  • Because a render only builds the hidden copy, React can pause it, throw it away or restart it without the screen ever showing a half-finished state.
  • Each DOM element points back to its fiber, which is how React DevTools shows component names for what you hover.

Try it: change the counters and watch the fiber tree

The sample app (a separate React root)

Reading the fiber tree…

Fibers are React 19.2 internals read through private fields (__reactContainer$…, memoizedState): fine for learning, never for app code, because they change between React versions.

Waiting for the first commit…

  • Do: Keep hooks at the top level of components, unconditionally, so their order never changes.

    Avoid: Reading __react* properties in application code: they are internal and change between React versions.

Read more:React: Rules of Hooks(opens in a new tab)React: Preserving and resetting state(opens in a new tab)

03

Reconciliation and keys

Comparing two trees in general is expensive, so React uses two cheap rules instead. Elements of a different type produce different trees, and among siblings, the key says which element is which.

  • Same type in the same position: React keeps the DOM node and the component's state, and only updates changed attributes and text.
  • Different type (even <div> to <section> with identical children): the old subtree is unmounted, DOM and state included, and a new one is mounted.
  • Without real keys, list items are matched by position. Insert at the top and React updates every row instead of inserting one, and each row's state stays at its old position, now under the wrong item.
  • A key is also a reset button: changing a component's key makes React treat it as a new component with fresh state.

Try it: apply a change and read the DOM operations

Change

The rendered tree (before). Each row's note is its own state; edit one before applying the change to follow it.

DOM operations React performed

Apply the change to record the DOM mutations.

  • Do: Key list items by a stable id from your data.

    Avoid: Index keys in lists that can be reordered, filtered or inserted into, and random keys (Math.random()) that remount every row on every render.

Read more:React: Rendering lists(opens in a new tab)Legacy React docs: Reconciliation(opens in a new tab)

04

Render and commit

An update happens in two phases. In the render phase React calls your components to get new elements and works out the changes; nothing touches the screen and the work can be interrupted. In the commit phase it applies the changes to the DOM in one go, then runs effects.

  • Render runs parent first (a parent's output decides its children). Effects run child first, because a child is complete before its parent is.
  • useLayoutEffect runs during the commit, after the DOM has changed but before the browser paints: the place to measure layout or adjust it without a visible flicker. It blocks painting, so keep it short.
  • useEffect runs later. For updates caused by a discrete event such as a click, React runs the effects before the browser paints the next frame, so the effect's result shows up with the click; for other updates they normally run after the paint.
  • A transition shows the scheduler at work: React first re-renders urgently (so isPending can turn true), lets the browser draw a frame, and only then renders the transition itself.
  • Render must be pure: React may call a component more than once for one commit (Strict Mode does it on purpose in development), or start a render and throw it away.

Try it: trigger updates three ways and read the order

The app is real React in its own root. A MutationObserver reports DOM changes in a microtask, so it appears just after the synchronous work that caused it.

Trigger an update to see its phases in order.

  • Do: Read layout in useLayoutEffect, and do everything else (subscriptions, fetching, logging) in useEffect or in event handlers.

    Avoid: Side effects in the component body: they can run twice, or for a render that never commits.

Read more:React: Render and commit(opens in a new tab)React: useLayoutEffect(opens in a new tab)React: useEffect(opens in a new tab)

05

Urgent and deferred updates

Not all updates are equally urgent. Typing must show at once; a list filtered by that typing can wait a moment. React gives updates priorities (lanes): urgent ones render synchronously, while transitions and deferred values render in small slices that yield to the browser and can be abandoned when newer input arrives.

  • A synchronous render can't be interrupted: if the list takes 125 ms, the browser can't paint the next keystroke for 125 ms.
  • useDeferredValue(query) renders the input with the new text first, then re-renders the list with the deferred value at low priority. If another key arrives meanwhile, React drops that render and starts again with the latest value.
  • The slow component must be memoized (memo) so the urgent render can skip it, and React can only pause between components: one component that does 100 ms of work in its body can't be interrupted. The demo's list is 250 small row components for that reason.
  • startTransition does the same for state updates you trigger yourself, such as switching tabs. Neither makes the slow work faster; they keep it from blocking more important work, which is what Interaction to Next Paint measures (see large dataset performance for making the work itself cheaper).

Try it: type into a slow list, urgent vs deferred

Mode
  • item 0 react
  • item 1 render
  • item 2 reconcile
  • item 3 fiber
  • item 4 hooks
  • item 5 commit
  • item 6 react
  • item 7 render
  • item 8 reconcile
  • item 9 fiber
  • item 10 hooks
  • item 11 commit
  • item 12 react
  • item 13 render
  • item 14 reconcile
  • item 15 fiber
  • item 16 hooks
  • item 17 commit
  • item 18 react
  • item 19 render
  • item 20 reconcile
  • item 21 fiber
  • item 22 hooks
  • item 23 commit
  • item 24 react
  • item 25 render
  • item 26 reconcile
  • item 27 fiber
  • item 28 hooks
  • item 29 commit
  • item 30 react
  • item 31 render
  • item 32 reconcile
  • item 33 fiber
  • item 34 hooks
  • item 35 commit
  • item 36 react
  • item 37 render
  • item 38 reconcile
  • item 39 fiber
  • item 40 hooks
  • item 41 commit
  • item 42 react
  • item 43 render
  • item 44 reconcile
  • item 45 fiber
  • item 46 hooks
  • item 47 commit
  • item 48 react
  • item 49 render
  • item 50 reconcile
  • item 51 fiber
  • item 52 hooks
  • item 53 commit
  • item 54 react
  • item 55 render
  • item 56 reconcile
  • item 57 fiber
  • item 58 hooks
  • item 59 commit
  • item 60 react
  • item 61 render
  • item 62 reconcile
  • item 63 fiber
  • item 64 hooks
  • item 65 commit
  • item 66 react
  • item 67 render
  • item 68 reconcile
  • item 69 fiber
  • item 70 hooks
  • item 71 commit
  • item 72 react
  • item 73 render
  • item 74 reconcile
  • item 75 fiber
  • item 76 hooks
  • item 77 commit
  • item 78 react
  • item 79 render
  • item 80 reconcile
  • item 81 fiber
  • item 82 hooks
  • item 83 commit
  • item 84 react
  • item 85 render
  • item 86 reconcile
  • item 87 fiber
  • item 88 hooks
  • item 89 commit
  • item 90 react
  • item 91 render
  • item 92 reconcile
  • item 93 fiber
  • item 94 hooks
  • item 95 commit
  • item 96 react
  • item 97 render
  • item 98 reconcile
  • item 99 fiber
  • item 100 hooks
  • item 101 commit
  • item 102 react
  • item 103 render
  • item 104 reconcile
  • item 105 fiber
  • item 106 hooks
  • item 107 commit
  • item 108 react
  • item 109 render
  • item 110 reconcile
  • item 111 fiber
  • item 112 hooks
  • item 113 commit
  • item 114 react
  • item 115 render
  • item 116 reconcile
  • item 117 fiber
  • item 118 hooks
  • item 119 commit
  • item 120 react
  • item 121 render
  • item 122 reconcile
  • item 123 fiber
  • item 124 hooks
  • item 125 commit
  • item 126 react
  • item 127 render
  • item 128 reconcile
  • item 129 fiber
  • item 130 hooks
  • item 131 commit
  • item 132 react
  • item 133 render
  • item 134 reconcile
  • item 135 fiber
  • item 136 hooks
  • item 137 commit
  • item 138 react
  • item 139 render
  • item 140 reconcile
  • item 141 fiber
  • item 142 hooks
  • item 143 commit
  • item 144 react
  • item 145 render
  • item 146 reconcile
  • item 147 fiber
  • item 148 hooks
  • item 149 commit
  • item 150 react
  • item 151 render
  • item 152 reconcile
  • item 153 fiber
  • item 154 hooks
  • item 155 commit
  • item 156 react
  • item 157 render
  • item 158 reconcile
  • item 159 fiber
  • item 160 hooks
  • item 161 commit
  • item 162 react
  • item 163 render
  • item 164 reconcile
  • item 165 fiber
  • item 166 hooks
  • item 167 commit
  • item 168 react
  • item 169 render
  • item 170 reconcile
  • item 171 fiber
  • item 172 hooks
  • item 173 commit
  • item 174 react
  • item 175 render
  • item 176 reconcile
  • item 177 fiber
  • item 178 hooks
  • item 179 commit
  • item 180 react
  • item 181 render
  • item 182 reconcile
  • item 183 fiber
  • item 184 hooks
  • item 185 commit
  • item 186 react
  • item 187 render
  • item 188 reconcile
  • item 189 fiber
  • item 190 hooks
  • item 191 commit
  • item 192 react
  • item 193 render
  • item 194 reconcile
  • item 195 fiber
  • item 196 hooks
  • item 197 commit
  • item 198 react
  • item 199 render
  • item 200 reconcile
  • item 201 fiber
  • item 202 hooks
  • item 203 commit
  • item 204 react
  • item 205 render
  • item 206 reconcile
  • item 207 fiber
  • item 208 hooks
  • item 209 commit
  • item 210 react
  • item 211 render
  • item 212 reconcile
  • item 213 fiber
  • item 214 hooks
  • item 215 commit
  • item 216 react
  • item 217 render
  • item 218 reconcile
  • item 219 fiber
  • item 220 hooks
  • item 221 commit
  • item 222 react
  • item 223 render
  • item 224 reconcile
  • item 225 fiber
  • item 226 hooks
  • item 227 commit
  • item 228 react
  • item 229 render
  • item 230 reconcile
  • item 231 fiber
  • item 232 hooks
  • item 233 commit
  • item 234 react
  • item 235 render
  • item 236 reconcile
  • item 237 fiber
  • item 238 hooks
  • item 239 commit
  • item 240 react
  • item 241 render
  • item 242 reconcile
  • item 243 fiber
  • item 244 hooks
  • item 245 commit
  • item 246 react
  • item 247 render
  • item 248 reconcile
  • item 249 fiber

Simulate typing, or type in the field yourself (results appear when it loses focus).

Latency is measured from each input event to the next frame's requestAnimationFrame, which can't start while React is busy rendering on the main thread.

  • Do: Defer expensive views that follow input (results, charts), and keep the input itself urgent.

    Avoid: Deferring the input's own value: typing then lags behind what the user pressed.

Read more:React: useDeferredValue(opens in a new tab)React: useTransition(opens in a new tab)web.dev: Interaction to Next Paint(opens in a new tab)