Part 0 · 4 chapters · ~30 min

What React Is

A component is a function, what it returns is a description, React keeps a persistent tree of its own, and the browser keeps the DOM. This part is the three trees and their lifetimes, one update traced from setState to pixels with the rule each phase imposes, the six places the pure model leaks and the hatch for each, and the map of the course.

1

Elements, components, fibers, DOM

the question

"Is a component an object? When I return JSX, what is it, and who holds it?"

A component is a function. What it returns is a tree of elements: plain, immutable objects describing what should exist, created on every render and discarded after reconciliation. React holds a separate, persistent tree of fibers: one per rendered position, each carrying state, the previous props, scheduling information, and (for host components) a pointer to a DOM node. The browser holds the DOM. The three trees have three lifetimes, and most confusion about "re-renders" and "losing state" comes from attributing one tree's behaviour to another.

code
// what JSX becomes, and what React does with it
const el = <main className="x"><List items={items} /></main>
// → jsx("main", { className: "x", children: jsx(List, { items }) })
// → { $$typeof: Symbol.for("react.element"), type: "main", key: null, ref: null, props: { className: "x", children: {…} } }
// an element is a frozen plain object. React never mutates it; it is compared (type, key) and read (props) during reconciliation, then dropped.

// a component is a function from props to elements. React calls it; you never do.
function List({ items }) { return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul> }
// List(…) would return elements without a fiber: no hooks, no identity. <List /> gives React a type to create a fiber for.

// the tree React keeps is fibers; the tree you return is elements; the tree the user sees is the DOM. three trees, three lifetimes.
the three, precisely
  1. Element: { type, key, ref, props }. type is a string (host) or a function or class (component) or a special symbol (Fragment, Suspense, Context provider). Immutable. Allocated per render; young-generation garbage (the JS course part 5). Compared by type and key during reconciliation; its props become the new fiber's pendingProps.
  2. Fiber: a mutable node that persists across renders at a given tree position while the element's type and key match. Holds memoizedState (the hook list), memoizedProps, stateNode (the DOM node or class instance), lanes, flags, and the child/sibling/return pointers. Two versions exist at once (current and work-in-progress, part 1).
  3. DOM node: created in commit for host fibers; updated by prop diffs; removed on deletion. The only tree the user sees and the only one that costs layout and paint.
what follows
  1. State lives on the fiber, not in the function. A component function has no memory between calls; useState reads and writes the fiber's hook list (part 4). A new fiber (new key, new type, new position) means fresh state.
  2. "Re-render" means "React called the function again and reconciled the result." The DOM changes only where the reconciled elements differ from the previous fibers' props. A component that renders 100 times and returns the same elements each time touches the DOM 0 times.
  3. Identity is position plus key plus type. Move an element to a different position without a key, or change its type, and it is a different fiber: unmount and mount.
ELEMENT, FIBER, DOM
three trees for one UI, and which one you touch
swipe the figure sideways, or tap expand for full screen
1/6
elements
is called by React: it returns an element tree: { type: Header, props: {} }, { type: "main", props: { children: [...] } }. Elements are immutable descriptions, created fresh every render, and thrown away after reconciliation. They have no state, no identity, no DOM.
2

One update, end to end

A state change becomes pixels through a fixed sequence: queue the update with a priority, schedule a render, run the render phase (pure, interruptible), run the commit phase (DOM mutation, synchronous), let the browser paint, then run passive effects. Each phase has a rule about what your code may do in it, and the rules follow from what React needs to be able to do with that phase: rerun it, interrupt it, or guarantee it happened.

the phases
  1. Update: setState(action) appends to the hook's update queue on the fiber, marks the fiber and its ancestors with a lane (part 3), and asks the root to schedule. Nothing renders synchronously; the state read in the same handler is still the old value.
  2. Schedule: sync lanes flush in a microtask at the end of the current event; other lanes go to the scheduler as a task at their priority. Updates sharing a lane are batched into one render.
  3. Render phase: the work loop walks to fibers with pending lanes; for each, beginWork calls the component (running its hooks) and reconciles the returned elements against the current child fibers (part 2); completeWork computes host prop diffs and bubbles effect flags upward. No DOM mutation; no effects. May be interrupted (non-sync lanes), restarted, or discarded; in Strict Mode, components are called twice to surface impurity.
  4. Commit phase: synchronous and uninterruptible. Before-mutation (snapshots); mutation (DOM inserts, updates, deletions; ref detach; cleanups of layout effects on deleted fibers; the current pointer is swapped here); layout (ref attach; useLayoutEffect; class componentDidMount/DidUpdate). The DOM is final at the end of mutation and visible after the next paint.
  5. Paint: the browser's turn (the browser course part 5).
  6. Passive effects: in a later task: cleanups from the previous render's useEffects, then this render's callbacks, parent-last order within each. A setState here starts a new cycle.
code
// how many times does this run? (more than you think)
function Price({ amount }) {
  console.log('render')                          // every render of Price: own state change, parent render, context change,
  const [open, setOpen] = useState(false)        // Strict Mode (twice per render in dev), a transition restart, Suspense retry
  useEffect(() => { console.log('effect') }, [])  // once after mount (twice in dev Strict Mode: mount, cleanup, mount)
  return <span onClick={() => setOpen(o => !o)}>{format(amount)}</span>
}
// the discipline: render has no side effects and no reliance on being called once. anything with a count belongs in an effect or a handler.
// format(amount) is called per render: fine if cheap; useMemo if measured as expensive; the React Compiler decides for you if enabled.
the rules, derived
  1. Render is pure because the render phase may run a component more than once, pause between fibers, or throw the result away. Side effects in render happen an unpredictable number of times.
  2. Layout effects are for measurement and synchronous DOM adjustment because they run before paint: the user never sees the intermediate state, and anything slow here delays the frame.
  3. Passive effects are for the outside world because they run after paint: the UI is already visible; a fetch or a subscription started here does not delay it.
  4. Refs are null during render because the DOM node does not exist until commit; they are set in the layout pass, so both kinds of effect can use them.
  5. Reading state right after setting it gives the old value because the queue is processed in the next render, not in the handler.
ONE UPDATE, END TO END
from setState to pixels
swipe the figure sideways, or tap expand for full screen
1/6
setState
Event: the click handler runs inside React's event system (a root listener dispatching synthetic events). setCount(c => c + 1): the updater is appended to the hook's update queue on the fiber; the fiber and its ancestors are marked with the event's lane (SyncLane for a click); ensureRootIsScheduled schedules a render callback. The handler returns; nothing has rendered.
3

Where the model leaks

"The UI is a function of state" is the model, and it is true of the element tree. Six things an application needs are not functions of state, and React gives each an escape hatch. The hatches are where the hard bugs are, because the model no longer protects you there.

the six, and their hatches
  1. DOM-owned state: focus, scroll, selection, an input's in-progress value, media playback. Not in your state; lost when the node is recreated. Hatch: keep nodes stable (keys, types) and read or restore via refs. For inputs React does not need to control, leave them uncontrolled (part 9).
  2. Effects on the outside world: network, subscriptions, timers, storage, the document title. Hatch: useEffect with a cleanup, keyed by a dependency array. The leak: dependencies compared by Object.is, so unstable references re-run effects; and Strict Mode's mount-cleanup-mount in development exists to prove the cleanup is correct.
  3. Imperative APIs: focus(), play(), scrollIntoView(), third-party widget methods. Hatch: a ref and an effect or handler; useImperativeHandle to expose a narrow imperative surface from a component. The leak: calling them during render.
  4. Time: transitions between states. Hatch: CSS transitions for property changes; the View Transitions API (<ViewTransition> in React 19.1+) for layout changes between commits; animation libraries for exit animations, which require keeping an element mounted after state says it is gone.
  5. Identity: which instance a component is, across renders and across server and client. Hatch: keys for list identity; useId for stable ids. The leak: a component defined inside another's render is a new type every render and remounts every time.
  6. The server: the first render happened elsewhere, with different inputs. Hatch: hydration rules (part 13); useSyncExternalStore with a server snapshot; environment reads deferred to effects. The leak: window, Date.now(), Math.random(), locale, localStorage in render.
the discipline
Inside render: pure functions of props, state and context. Everything else in a hook that names which hatch it is using. When a bug involves focus, timing, a double-run, or "it works on the client only", look at the hatch first.
WHERE "UI = f(STATE)" LEAKS
six places the pure model needs an escape hatch
swipe the figure sideways, or tap expand for full screen
1/6
DOM state
The DOM has state React does not render: focus, scroll position, text selection, an input's uncommitted value, a video's playback position. A re-render that recreates a node (a changed key or type, part 2) loses it. Hatch: refs and stable identity; uncontrolled inputs for values React need not know.
4

The map of this course

the internals (parts 1 to 6)
  1. P1 Fiber: the node's fields, the two trees and the alternate pointer, the work loop, beginWork by fiber tag, completeWork and host instances, the effect list, the commit passes in order.
  2. P2 Reconciliation: the heuristics, single-child and array reconciliation, keys, bailouts and the memo primitives as modifications to them, what each costs.
  3. P3 The scheduler and lanes: event priority to lane, the scheduler package's heaps and slices, lane selection, interruption and restart, expiration, entanglement, how a transition differs from a sync update.
  4. P4 Hooks: the hook list on the fiber, mount versus update dispatchers, useState and useReducer queues, effect objects and their tags, the timing of each effect kind, refs, useMemo and useCallback, useSyncExternalStore, useId, the rules and the mechanism behind each.
  5. P5 Concurrent rendering: interruptible rendering, transitions and useDeferredValue, Suspense as thrown promises and boundaries, streaming SSR, selective hydration, what tearing is and how useSyncExternalStore prevents it.
  6. P6 Server Components and the compiler: the RSC payload format, server versus client boundaries, serialisation rules, server actions, and the React Compiler's analysis and output.
the practice (parts 7 to 13)
  1. P7 State management at scale: local, lifted, context, store, URL, server; when each; Redux, Zustand, Jotai, XState, query caches, and nothing.
  2. P8 Data fetching patterns: fetch-on-render and its waterfalls, render-as-you-fetch, caching and invalidation, optimistic updates, mutations, the query-cache model.
  3. P9 Forms and inputs: controlled and uncontrolled, validation timing, large forms, the money form, actions and useActionState.
  4. P10 Patterns: composition, compound components, render props and hooks, state colocation, headless and polymorphic components, slots.
  5. P11 Anti-patterns at scale: prop drilling, context overuse, effect chains, derived state in state, premature memo, and the fix for each.
  6. P12 Testing React: Testing Library's philosophy, component and integration tests, what to mock, visual regression.
  7. P13 React on the wire: hydration and mismatches, islands and partial hydration, hybrid apps and WebViews.
how to read it
Parts 1 to 4 once, in order; they are what the rest refers to. Then the practice parts as the problem in front of you demands, each self-contained.