05 · React Internals & Patterns
React as a system: the Fiber reconciler, the Lanes priority model, the hooks runtime, concurrent rendering, Server Components, and the Compiler. Plus the patterns that survive framework churn. Current as of React 19.2 with the React Compiler 1.0 (stable).
Positioning
React is two things stacked: a reconciler (the diffing/scheduling engine, react-reconciler) and a renderer (the host binding — react-dom, react-native, custom renderers). The reconciler computes what should change; the renderer applies it to a host (DOM, native views, even PDFs or terminals). Understanding this split explains hooks, concurrency, RSC, and most “why did it re-render” questions.
Foundations: the mental model
React’s core idea: UI = f(state). You describe the UI you want for the current state; React figures out the minimal mutations to get the host there. This is declarative rendering. The machinery that makes it efficient is the virtual DOM (a lightweight tree of element descriptors) plus a reconciler that diffs the new tree against the committed one.
But “virtual DOM diffing” is the 2015 story. The 2026 story is Fiber + Lanes + concurrent rendering: React can render in the background, pause, prioritize, and abandon work — because rendering is now interruptible.
Deep dive
1. Elements, components, and the two trees
- A React element is a plain object:
{ type, props, key }— what JSX compiles to (jsx(type, props, key)via the automatic runtime). It’s a description, immutable and cheap. - A component is a function (or legacy class) returning elements.
- React maintains the current tree (what’s on screen) and builds a work-in-progress tree during rendering. This is double buffering — exactly like a graphics pipeline holding a front buffer (displayed) and back buffer (being drawn), then swapping. The swap is the commit.
2. Fiber: the unit of work
A Fiber is a JS object representing a node in the work-in-progress tree and a unit of work. Key fields:
type,key,stateNode(the host instance or component instance).child,sibling,return— a singly-linked tree that the reconciler walks iteratively (not recursively), so it can pause and resume.pendingProps/memoizedProps,memoizedState(the hooks linked list for function components),updateQueue.flags(effect tags: Placement, Update, Deletion),lanes(priority),alternate(the link between current and WIP fiber — the double buffer).
Fiber’s whole reason for existing: replace React 15’s synchronous recursive stack reconciler with a resumable, prioritizable one. Because work is a linked list of fibers, React can do a bit of work, check “am I out of time / did something more urgent arrive?”, yield to the browser, and continue later.
3. The two phases: render and commit
- Render phase (interruptible, no side effects) — React walks the fiber tree calling
beginWork(start a fiber: run the component, diff children, mark effects) down to leaves, thencompleteWorkback up (build/prepare host instances, bubble effect flags). This phase is pure and can be paused, restarted, or thrown away. That’s why your render function must be side-effect-free and idempotent (and whyStrictModedouble-invokes it in dev to catch impurity). - Commit phase (synchronous, atomic) — React applies the effects to the host in three sub-phases: before mutation (snapshots,
getSnapshotBeforeUpdate), mutation (DOM insertions/updates/deletions, ref detach), layout (useLayoutEffect, ref attach — runs synchronously before paint). Then the browser paints, and passive effects (useEffect) flush asynchronously after.
Reconciliation diffing heuristics: same type → reuse and update; different type → tear down and rebuild; lists use key for identity. Keys must be stable and unique among siblings — using array index as key breaks identity on reorder/insert (state attaches to the wrong item). This is the #1 list bug.
4. Lanes: the priority model
Lanes are a 31-bit bitmask encoding the priority of pending updates. Instead of a single priority number, lanes let React batch and prioritize multiple in-flight updates with bitwise ops. Rough priority ordering:
- SyncLane — discrete user input (click, keypress) that must feel instant.
- InputContinuousLane — continuous input (drag, hover).
- DefaultLane — normal updates (network responses,
setState). - TransitionLanes —
startTransition/useTransitionupdates: explicitly non-urgent, interruptible, can be deferred so urgent work jumps the queue. - IdleLane / OffscreenLane — lowest priority, prerendering hidden content (
<Activity>/ Offscreen).
The scheduler ties into the browser via cooperative yielding (historically a MessageChannel-based scheduler approximating requestIdleCallback; the platform’s scheduler.postTask/scheduler.yield are increasingly relevant). This is how time-slicing keeps the main thread responsive during big renders.
5. Concurrent features (the user-facing surface of all the above)
useTransition()/startTransition(fn)— mark a state update as a non-urgent transition so the UI stays responsive (typing stays snappy while an expensive filtered list re-renders in the background). React 19 allows async functions in transitions (the “Actions” model) — pending state is managed for you.useDeferredValue(value)— render a deferred copy of a fast-changing value at lower priority.<Suspense>— declarative loading boundaries. A component “suspends” by throwing a promise (conceptually); React shows the nearestfallbackuntil it resolves. The backbone of streaming SSR, lazy loading, and data fetching.useActionState,useOptimistic,useFormStatus(React 19) — first-class async form/mutation handling with optimistic UI.use(promise)/use(context)(React 19) — read a promise or context conditionally; the data-fetching primitive that integrates with Suspense.useEffectEvent(19.2 stable) — extract non-reactive logic from effects, fixing the “I want the latest value but don’t want it in the dependency array” problem cleanly.<Activity>(19.2) — render background/hidden UI (display:none) while preserving state and tearing down effects; the modern Offscreen.- View Transitions (
<ViewTransition>, 19.2) — animate elements across state/route changes using the platform View Transitions API.
6. Hooks: the runtime
Hooks are not magic — they’re a linked list of “memoized state” cells attached to the fiber, read in call order. That’s why the Rules of Hooks exist: call them unconditionally, at the top level, in the same order every render — otherwise the list indices misalign.
useState/useReducer— append/read a state cell; the setter is a closure over the fiber and enqueues an update on a lane. Updates are batched (React 18+ batches across promises/timeouts/native events automatically — “automatic batching”).useRef— a mutable box ({ current }) that persists across renders without triggering them; escape hatch to imperative DOM and to “latest value” patterns.useMemo/useCallback— cache a value/function across renders by dependency identity. In the Compiler era these become largely unnecessary (see §8).useEffect— synchronize with external systems after paint; cleanup runs before the next effect and on unmount. Its dependency array is a correctness mechanism (which external values the effect reads), not an optimization. Most “effect bugs” are really “this shouldn’t be an effect” — derive during render, handle events in handlers, and reserve effects for genuine external synchronization (subscriptions, DOM measurements, non-React widgets).useLayoutEffect— same, but synchronous before paint (for measurements that must avoid flicker).useSyncExternalStore— the official bridge for subscribing to external stores (Redux, Zustand, browser APIs) that’s safe under concurrent rendering and SSR (prevents tearing — see06).useId— stable IDs across SSR/hydration.
7. React Server Components (RSC) — the architectural shift
RSC splits components into Server Components (run only on the server, never shipped to the client, can be async and touch the database/filesystem directly) and Client Components ('use client', interactive, shipped as JS). The server renders a serialized RSC payload (a description of the UI tree, not HTML) that the client uses to reconcile. Benefits: zero client JS for server-only components, data fetching co-located on the server, smaller bundles. Constraints: server components can’t use state/effects/browser APIs; the 'use client' / 'use server' directives mark the boundaries. Server Actions ('use server' functions) let client components call server functions directly (form submissions, mutations). Stable in React 19; the underlying bundler APIs are framework territory (see 08-nextjs-and-meta-frameworks.md). Security note: the RSC protocol had serious 2025 CVEs — keep frameworks patched (17).
8. The React Compiler (stable, 1.0)
The Compiler auto-memoizes your components at build time — analyzing data flow to insert the equivalent of useMemo/useCallback/memo at finer granularity than humans reliably do, without stale-deps risk. Consequences:
- You write plain functions and values; manual memoization becomes mostly obsolete (lint rules enforcing
useCallbackfor prop stability are retired). - It ships as a build plugin (Babel, and increasingly SWC/oxc; Next.js 15.3.1+/16 integrate it via SWC).
- It only works if your code follows the Rules of React (purity, no mutation of props/state during render). It conservatively skips components it can’t prove safe.
react-hooks/exhaustive-depsstill matters foruseEffect(effects keep dependency arrays).
The practical takeaway: stop hand-optimizing re-renders; fix correctness (Rules of React) and let the Compiler memoize. Reserve manual memo/useMemo for measured hotspots the Compiler skips.
9. Patterns (the durable layer)
- Composition over configuration —
children, slots, and component composition beat boolean-prop explosions. - Container/presentational is mostly dead; replaced by custom hooks (extract behavior) + headless components (logic without markup — Radix, React Aria, TanStack).
- Compound components —
<Tabs><Tab/><TabPanel/></Tabs>sharing implicit state via context. - Render props / function-as-children — still useful for inversion of control; largely superseded by hooks.
- Provider pattern — context for dependency injection / theming; beware context as a state-management tool (every consumer re-renders on change — see
06). - State colocation — keep state as low as possible; lift only when shared. Most “state management” problems dissolve with proper colocation + server-state libraries.
- Error boundaries — class components (or
react-error-boundary) that catch render errors; pair with Suspense for loading+error.
Worked example: keeping input snappy under heavy rendering
function Search({ allItems }) {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function onChange(e) {
setQuery(e.target.value); // urgent: input stays responsive
startTransition(() => { // non-urgent: the expensive list
setFilter(e.target.value);
});
}
const results = useMemo( // (Compiler may make this redundant)
() => allItems.filter(i => i.includes(filter)),
[allItems, filter]
);
return (
<>
<input value={query} onChange={onChange} />
{isPending && <Spinner />}
<List items={results} />
</>
);
}
The transition lets React keep the keystroke update (SyncLane) ahead of the filtered re-render (TransitionLane), interrupting the list render if you type again.
Pitfalls & gotchas
- Array index as
key→ state/DOM attaches to the wrong item on reorder. - Effects used as derived state → cascading renders, bugs; derive during render instead.
- Missing/incorrect effect deps → stale closures (see
02); useuseEffectEventfor non-reactive reads. - Context for high-frequency state → every consumer re-renders; split contexts or use a store with selectors.
- Mutating state/props → breaks reconciliation and the Compiler’s assumptions; always produce new references.
- Premature
memo/useMemoin the Compiler era — measure first. - Treating render as a place for side effects → impurity surfaces under StrictMode/concurrent rendering.
- Suspense waterfalls — fetching inside nested suspended children serializes requests; hoist/parallelize fetches.
Interview / self-test questions
- What is a Fiber and why did React move from the stack reconciler to it? (Resumable, prioritizable units of work.)
- Walk through render vs commit phases and which is interruptible.
- What are Lanes and how do transitions use them?
- Why must hooks be called in the same order every render? (They’re a positional linked list on the fiber.)
- When is
useEffectthe wrong tool? (Derived state, transforming data, responding to events — handle those elsewhere.) - Explain
useSyncExternalStoreand the tearing problem it solves. - What changes with the React Compiler — what do you stop writing?
- Server vs Client Components — capabilities and constraints of each.
- Why is index-as-key dangerous? Give a concrete bug.
- How does
<Suspense>actually work, and how does it enable streaming SSR?
Recommendations
- Adopt the React Compiler; delete manual memoization except measured hotspots.
- Follow the Rules of React religiously — it’s now a prerequisite for the compiler’s optimizations.
- Colocate state; use server-state libraries for server data (
06); reserve global client state for genuinely global UI. - Use transitions/
useDeferredValuefor expensive updates; keep input on the sync lane. - Treat effects as “synchronize with external systems,” not “respond to state.”
- Lean on headless libraries (Radix, React Aria, TanStack) for accessible, unstyled primitives (
11,19).
Books & references
- react.dev — the official docs are a top-tier textbook: “Thinking in React,” “You Might Not Need an Effect,” “Escape Hatches,” the Compiler docs, and the 19/19.2 release notes.
- “Fluent React” — Tejas Kumar (2024). The best current deep dive on Fiber, reconciliation, concurrency, and RSC.
- “The Joy of React” — Josh Comeau; “Epic React” — Kent C. Dodds. Community-favorite practical mastery courses.
- “React 19 Deep Dive” / “React for Two Computers” / “React Compiler Deep Dive” — talks linked from react.dev/versions.
- “Build your own React” — Rodrigo Pombo (pomb.us/build-your-own-react). Implement a mini-Fiber to truly understand it.
- Dan Abramov’s writing (overreacted.io) — “A Complete Guide to useEffect,” “React as a UI Runtime” — foundational mental models.
Connections
01&02— double buffering ↔ graphics pipeline; the scheduler ↔ the event loop; hooks ↔ closures.06-state-management-and-stores.md—useSyncExternalStore, context limits, server vs client state.07-rendering-strategies.md— Suspense/streaming/RSC are the rendering-strategy substrate.08-nextjs-and-meta-frameworks.md— the framework that wires RSC, Server Actions, and the Compiler together.15-performance-and-core-web-vitals.md— concurrency and transitions target INP.