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.
Elements, components, fibers, DOM
"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.
// 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.- Element:
{ type, key, ref, props }.typeis 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 bytypeandkeyduring reconciliation; its props become the new fiber'spendingProps. - 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). - 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.
- State lives on the fiber, not in the function. A component function has no memory between calls;
useStatereads and writes the fiber's hook list (part 4). A new fiber (new key, new type, new position) means fresh state. - "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.
- 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.
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.
- 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. - 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.
- Render phase: the work loop walks to fibers with pending lanes; for each,
beginWorkcalls the component (running its hooks) and reconciles the returned elements against the current child fibers (part 2);completeWorkcomputes 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. - 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; classcomponentDidMount/DidUpdate). The DOM is final at the end of mutation and visible after the next paint. - Paint: the browser's turn (the browser course part 5).
- Passive effects: in a later task: cleanups from the previous render's
useEffects, then this render's callbacks, parent-last order within each. AsetStatehere starts a new cycle.
// 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.- 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.
- 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.
- 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.
- 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.
- Reading state right after setting it gives the old value because the queue is processed in the next render, not in the handler.
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.
- 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).
- Effects on the outside world: network, subscriptions, timers, storage, the document title. Hatch:
useEffectwith a cleanup, keyed by a dependency array. The leak: dependencies compared byObject.is, so unstable references re-run effects; and Strict Mode's mount-cleanup-mount in development exists to prove the cleanup is correct. - Imperative APIs:
focus(),play(),scrollIntoView(), third-party widget methods. Hatch: a ref and an effect or handler;useImperativeHandleto expose a narrow imperative surface from a component. The leak: calling them during render. - 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. - Identity: which instance a component is, across renders and across server and client. Hatch: keys for list identity;
useIdfor stable ids. The leak: a component defined inside another's render is a new type every render and remounts every time. - The server: the first render happened elsewhere, with different inputs. Hatch: hydration rules (part 13);
useSyncExternalStorewith a server snapshot; environment reads deferred to effects. The leak:window,Date.now(),Math.random(), locale,localStoragein render.
The map of this course
- 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.
- P2 Reconciliation: the heuristics, single-child and array reconciliation, keys, bailouts and the memo primitives as modifications to them, what each costs.
- 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.
- 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.
- 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.
- 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.
- P7 State management at scale: local, lifted, context, store, URL, server; when each; Redux, Zustand, Jotai, XState, query caches, and nothing.
- P8 Data fetching patterns: fetch-on-render and its waterfalls, render-as-you-fetch, caching and invalidation, optimistic updates, mutations, the query-cache model.
- P9 Forms and inputs: controlled and uncontrolled, validation timing, large forms, the money form, actions and useActionState.
- P10 Patterns: composition, compound components, render props and hooks, state colocation, headless and polymorphic components, slots.
- P11 Anti-patterns at scale: prop drilling, context overuse, effect chains, derived state in state, premature memo, and the fix for each.
- P12 Testing React: Testing Library's philosophy, component and integration tests, what to mock, visual regression.
- P13 React on the wire: hydration and mismatches, islands and partial hydration, hybrid apps and WebViews.