React Architecture: How It Actually Works
How to read this page
Symvanta parsed this repository into a code graph: every function, class, and method is a node, and every call or import between them is an edge. Everything on this page is computed from that graph at the commit shown above. The terms:
- Module (or cluster)
- A group of symbols that call each other far more than they call anything else. An algorithm called Louvain community detection finds these groups from the call traffic alone; nobody draws them by hand.
- Modularity (the Q number)
- A 0-to-1 score of how cleanly those groups separate. Higher means more call traffic stays inside its own group; scores around 0.7 and above read as clean boundaries.
- Hub
- The most depended-upon symbol inside one module.
- Load-bearing symbols
- PageRank, the algorithm Google originally used to rank web pages, run over the call graph instead: it surfaces the functions the rest of the codebase leans on hardest.
- Arrows and their numbers
- How many calls cross from one module into another. A heavier arrow means tighter coupling between those two parts.
- Dependency cycle
- File A imports B, which imports A again, sometimes through a longer loop. Cycles are not bugs, but a change inside one tends to ripple around the whole loop.
- Mutually recursive symbols
- Functions that call each other, usually the natural shape of parsers and tree-walking code.
React is Meta's UI library and the renderer, compiler, and devtools ecosystem that ships alongside it in one monorepo: the core react package that exposes hooks and createElement, the Fiber reconciler that schedules and commits updates, per-target renderers (react-dom, react-native-renderer, react-test-renderer, react-art), the Flight protocol for React Server Components (react-server, react-client), the React Compiler that auto-memoizes components at build time, and React DevTools for inspecting all of it at runtime. Symvanta's Louvain community detection organized the codebase's indexed symbols into 381 functional clusters (modularity Q=0.85, a clean separation of concerns for a codebase that ships five different products out of one source tree). The single largest cluster, at 3,086 symbols, is the shared Jest surface (hub Component): the expect, describe, it, and fixture components every package's suite reaches for, excluded from the diagram below the same way etcd's end-to-end harness was excluded from its own map. Underneath that sits the real product shape, and two clusters tie for the top of it at 1,980 symbols each. One is the reconciler plus the shared React types (hub Fiber), holding Fiber, FiberRoot, Lanes, ReactContext, and ReactSharedInternals together. The other is the React Compiler's HIR machinery (hub GeneratedSource: source-location and identifier tracking for lowered code). React DevTools contributes two of the ten largest, the frontend store and inspection types (1,167 symbols, hub ReactCallSite, which also absorbs the profiler timeline's Rect geometry) and the backend bridge's element-kind types (872 symbols, hub ElementType), and the Flight protocol contributes a third (1,113 symbols, hub ReactComponentInfo).
This page is about the library's own internals. If you came here for how to structure a React project of your own (feature folders, module boundaries, dependency direction), that is a different question, and the companion post answers it.
Module map
The diagram below shows the 10 largest of 381 detected modules, sized by symbol count, with arrows weighted by how many calls cross between them. Every module is labelled after the package its hub symbol actually lives in, checked one hub at a time against the graph, because the names Symvanta's summarizer assigned this repo describe the shape of the code without placing it: the reconciler arrived as React Fiber Implementation, the compiler's HIR as Code Location Tracking, the DevTools frontend as React State Management, the DevTools backend as Element Interaction Tools, the Flight protocol as React Debugging Utilities, the react-dom host config as DOM Element Management, the server bindings as Rendering State Management, the public API and Fizz core as Request Resolution Logic, the compiler's fixture corpus as Object Manipulation Utilities, and the monorepo's own build scripts as Execution Engine. The Jest cluster (3,086 symbols, hub Component) is excluded, the same way etcd's end-to-end test harness was excluded from its map: it exercises every package's suite rather than forming part of any one of them.
Where to start reading
These 16 symbols blend the hub of each product-shaped cluster above with the load-bearing entries from Symvanta's PageRank ranking, filtered to drop the fixture and tooling noise the same PageRank pass surfaced: identity (a pass-through helper in the compiler's snap fixture runtime), expect (Jest's assertion function), cloneAst (a Babel AST-cloning helper in the compiler's snap minimizer), Button (a component in the Server Components demo app under fixtures/flight), ForkContext.<get>head (an internal accessor from eslint-plugin-react-hooks, the lint package React ships beside the library), createError (a helper inside a DevTools end-to-end script), createMouseEvent (an event factory from dom-event-testing-library), and abort (a bare name that resolves to the DOM AbortController the Fizz, Flight, and DOM abort paths all reach for). Start here to understand how React's pieces connect:
FiberReactContextGeneratedSourceReactCallSiteReactComponentInfoElementTypeDOMEventNameInstanceReactNodeListcreateElementuseStateuseEffectresolveDispatcherrunWithFiberInDEVCompilerError.invariantreportGlobalError
A few of these are worth calling out individually. Fiber and FiberRoot (same file) are the reconciler's core data structures: one unit of work and the root that owns a tree of them. At this commit the reconciler cluster also absorbs the shared React types, so ReactContext, ReactSharedInternals, and Lanes all live inside react-reconciler alongside Fiber, which is the cluster's hub. useState and useEffect are the public hooks; both call resolveDispatcher first to find the dispatcher the reconciler installed, covered in depth in the hooks dispatcher spoke. react-dom-bindings defines a second, unrelated resolveDispatcher for form actions; the one linked above is the hooks entry point in packages/react. runWithFiberInDEV is how the reconciler attaches the current fiber to a callback so a development warning can name the component it came from. GeneratedSource is the React Compiler's HIR source-tracking type, threaded through every instruction the compiler lowers so it can point errors back at the original source. ReactCallSite, ReactComponentInfo, ElementType, DOMEventName, Instance, and ReactNodeList are Flow interfaces: the shared vocabulary the reconciler, DevTools, Flight, the DOM host config, and the server renderer pass values through. CompilerError.invariant is the assertion helper the React Compiler calls when it hits a state its own passes should never produce. reportGlobalError is where a Server Components client gives up on a Flight stream and rejects every chunk still pending.
Key subsystems
The Fiber reconciler and the shared types
react-reconciler, 1,980 symbols, hub Fiber. This is the scheduler and commit engine every renderer shares, clustered together with the shared React type vocabulary: it walks a tree of Fiber nodes rooted at a FiberRoot, assigns each unit of work a priority Lane, and holds ReactContext, ReactSharedInternals, and Wakeable in the same community. It sits at both ends of the busiest traffic on the map. Outbound it runs into react-api-and-fizz 430 times, the single heaviest edge in the codebase, and into the react-dom host layer 217 times. Inbound, the host layer calls it 229 times, the React API and Fizz cluster 133, DevTools' bridge 79, and Flight 55. The Fiber reconciler spoke traces a setState call from scheduling through commit.
The React API and the Fizz render core
react-api-and-fizz, 717 symbols, hub __DEV__. Two things share this community because both reference the development-mode flag from nearly every file, and the flag itself is the hub: its graph node is the ambient declaration in scripts/jest/typescript/jest.d.ts. The first is the public react package: useState, useEffect, createElement, createContext, lazy, startTransition, memo, forwardRef, and resolveDispatcher. The second is the Fizz server's render core in packages/react-server/src/ReactFizzServer.js: renderNode, renderElement, performWork, flushCompletedQueues, and the SuspenseBoundary and ResumeSlots types they operate on. It takes 430 calls from the reconciler, 169 from Flight, 111 from the server bindings, and 106 from the host layer, and sends 133 back into the reconciler. The dispatcher-resolution logic every hook goes through is dissected in the hooks dispatcher spoke.
React Compiler: HIR and source tracking
compiler-hir, 1,980 symbols, hub GeneratedSource, tied with the reconciler for the largest cluster on the map and the largest single-package one. The compiler's high-level intermediate representation: SourceLocation, Place, Identifier, BlockId, and InstructionId are the pieces every lowering pass threads through. Its heaviest edge runs into the Flood type model (22) in compiler/packages/babel-plugin-react-compiler/src/Flood, as inference resolves the types an instruction operates on. Nothing in the runtime half of the monorepo calls it: its inbound traffic comes from the compiler's HIR debug printer (48), the Flood type model again (21), and the Rust rewrite's type configuration under compiler/crates (12).
React DevTools
Two clusters belong squarely to DevTools. devtools-frontend (1,167 symbols, hub ReactCallSite) is the frontend's inspection surface, holding SerializedElement, Store, StoreContext, SuspenseNode, and the profiler timeline's Rect geometry that used to cluster on its own. devtools-backend (872 symbols, hub ElementType) is the backend and bridge: element kinds, Agent, HostInstance, DevToolsInstance, and the component filters. The two call into each other more than into anything else (devtools-frontend into devtools-backend 125 times, devtools-backend back 88), with Flight next (100 and 71) and the reconciler after that (12 and 79), the inspector and the timeline reading live fiber state.
Flight and Server Components
flight-protocol, 1,113 symbols, hub ReactComponentInfo, carries the Flight protocol's component and request metadata: ReactDebugInfo, Thenable, ReactClientValue, ReactStackTrace, Request. It calls into react-api-and-fizz 169 times, the reconciler 55, and the DevTools frontend 51, the point where a resolved server payload becomes fiber updates on the client and something DevTools can display. The client half sits in its own smaller cluster of 221 symbols hubbed on Response, where reportGlobalError rejects every chunk still pending when a stream breaks; it sends 77 calls back into this one and 75 into react-api-and-fizz.
react-dom host layer
react-dom-bindings, 897 symbols, hub DOMEventName. The react-dom host config: DOMEventName, Instance, Container, TextInstance, SuspenseInstance, and ReactDOMSharedInternals, the types and writes the reconciler drives to mutate the actual DOM. Its 229 calls into react-reconciler are the second-heaviest edge on the map, and the reconciler answers with 217.
The server bindings
react-dom-server, 607 symbols, hub ReactNodeList. React's server-rendering entry points and DOM configuration: renderToPipeableStream in packages/react-dom/src/server, plus RenderState, StylesheetResource, ResumableState, and the createRequest setup path. It calls into react-api-and-fizz 111 times, where the Fizz render loop it configures actually lives, then Flight 20 and the host layer 11.
React Compiler fixture corpus
compiler-fixtures, 733 symbols, hub identity, is the React Compiler's own fixture mass: small sample React components (makeObject_Primitives, useIdentity, makeArray, useHook) that the compiler's snapshot suite compiles and re-compiles to check the transform's output, plus the snap shared runtime in compiler/packages/snap/src/sprout/shared-runtime.ts they import. Its hub identity is a pass-through helper from that runtime, which is why the load-bearing list above trims it. Outbound traffic is almost nil (5 calls into the Jest surface, then 1 each into a hooks fixture cluster, DevTools, and the build tooling), exactly what a corpus of standalone fixtures should look like.
Repository build tooling
repo-tooling, 583 symbols, hub cloneAst, is the monorepo's own scripts and the Node surface they type against: asyncCopyTo in scripts/bench/build.js, cloneAst in the compiler's snap minimizer, main and exec entry points from the build scripts, and the child_process and fs declarations in flow-typed/environments/node.js. It ships in no React package. It reaches the top ten on size alone, which is what a monorepo that carries its own benchmark harness, release scripts, and Flow libdefs looks like from the graph's side.
Canonical request flow
React has no HTTP surface of its own, so the flow traced here is a load-bearing library call instead: what happens when a function component calls useEffect.
useEffectis the public hook. In development it warns if the effect callback is missing, then callsresolveDispatcher()and delegates todispatcher.useEffect(create, deps).resolveDispatcherreadsReactSharedInternals.H. Every other hook in the file (useState,useContext,useRef, and the rest) calls the same function first. IfHisnull, development mode logs the "Invalid hook call" warning readers of the React docs will recognize, then returns the dispatcher anyway (the source comment explains that it avoids throwing its own error to keep this hot path inlinable).ReactSharedInternalsis the module-level singleton whoseHfield points at whichever hooks dispatcher is currently active.- That field is set by
renderWithHooksin the Fiber reconciler, immediately before it calls the function component being rendered. It installs one of two concrete implementations of theDispatcherinterface, a mount dispatcher on a component's first render and an update dispatcher on every render after, which is why the sameuseEffectcall allocates a new hook on mount but reuses its slot on update. When the render finishes,finishRenderingHooksswaps inContextOnlyDispatcherinstead of clearing the slot.
Outside render H therefore holds ContextOnlyDispatcher, whose hook members all point at throwInvalidHookError, so a hook called from a click handler or a module body throws the "Invalid hook call" error from there. The null check in resolveDispatcher fires only before anything has rendered at all, while H still holds its initial null. The hooks dispatcher spoke walks the full mount-vs-update path.
Health signals
Symvanta detected 15 dependency cycles across 381 modules (modularity Q=0.85). The largest spans 116 files in the ReactiveScopes area, entirely inside the React Compiler's babel-plugin-react-compiler package: the HIR, SSA, and reactive-scope lowering passes reference each other's types and visitor interfaces while walking a component down to its memoized form. The second, Components (115 files), is React DevTools' view layer (DevTools.js, InspectedElementStateTree.js, WhatChanged.js, ErrorBoundary/TimeoutView.js, and roughly 110 more), where panel components reference the views they render and the views reference the panels that host them. The third, react-reconciler (91 files), is the fiber walkers and commit-phase functions calling back and forth across the render and commit modules. The remaining twelve are small: the react-dom event plugins (21 files) and the DevTools element overlay (14 files) are the next two down, and ten of the fifteen span fewer than ten files each. None of the three large ones is a defect so much as the expected shape of a multi-pass compiler, a UI layer, and a tree reconciler.
Symvanta also detected 95 sets of mutually recursive symbols, the largest being react-reconciler (142 symbols: completeRoot, commitPassiveUnmountOnFiber, commitShowHideSuspenseBoundary, flushSyncWorkOnAllRoots, and the rest of the walkers that recurse into a fiber's children and back up). Flood (68 symbols) and HIR (51 symbols) follow, the React Compiler's structurally recursive Flow type model and its instruction-lowering functions, then react-server (40 symbols), the Flight and Fizz task-rendering functions. All four recurse for the same reason: they walk a tree whose nodes contain more of the same kind of node. A modularity Q of 0.85 across 381 modules indicates most of React's call traffic stays inside its own cluster despite the monorepo housing five separate products, and the cycles are concentrated in the three places a compiler, a UI layer, and a reconciler are expected to have them.
Auto-generated by Symvanta from the public repo facebook/react at commit eafeac0 , licensed MIT .
Machine-readable companion: data.json (module counts, subsystems, load-bearing symbols, health signals).