Architecture

React Architecture: How the Codebase Actually Works

facebook/react MIT 1 diagram
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 365 functional clusters (modularity Q=0.84, a clean separation of concerns for a codebase that ships five different products out of one source tree). The single largest cluster, at 3,055 symbols, is __tests__: Jest's own expect, describe, it, and the fixture components every package's test suite shares, 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: the Fiber reconciler (2,686 symbols, hub __DEV__, a development-mode flag referenced from nearly every file rather than a function worth reading) schedules and commits Fiber nodes under a FiberRoot, prioritized by Lanes. The core react package (ReactContext cluster, 1,172 symbols) is where every hook, useEffect included, resolves its current dispatcher through resolveDispatcher before doing any work. The React Compiler contributes both its real HIR machinery (GeneratedSource, 2,061 symbols: source-location and identifier tracking for lowered code) and a separate 719-symbol cluster of small sample components its own test suite compiles against. React DevTools contributes three of the ten largest clusters (ReactCallSite, ElementType, Rect): the call-site tracking, element-kind, and timeline-geometry types the DevTools frontend and backend bridge share.

Module map

The diagram below shows the 10 largest of 365 detected modules, sized by symbol count, with arrows weighted by how many calls cross between them. The __tests__ cluster (3,055 symbols: Jest's expect, describe, it, and shared fixture components) is excluded, the same way etcd's end-to-end test harness was excluded from its map: it exercises every package's test suite rather than forming part of any one of them. Two of the ten shown here, compiler and theme, carry real test-fixture and tooling noise of their own, explained honestly below and in Key subsystems, but are drawn as-is rather than filtered out, since they are communities React's own call graph actually produced, not artifacts of the detection algorithm.

facebook/react module map: the 10 largest of 365 detected modules with call-weighted edges, generated by Symvanta
Module map of facebook/react, generated by Symvanta. Link to this diagram Open full size

Where to start reading

These 13 symbols blend the hub of each product-shaped cluster above with the load-bearing entries from Symvanta's PageRank ranking, filtered to drop pure test and tooling noise the same PageRank pass surfaced: identity (a pass-through helper in the compiler's fixture-testing runtime), expect (Jest's assertion function, home cluster __tests__), Button (a fixture component), cloneAst (a Babel AST-cloning helper used only by compiler fixtures), and ForkContext.<get>head (an internal accessor from eslint-plugin-react-hooks, not React itself). Start here to understand how React's pieces connect:

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. ReactContext anchors the core react package's type definitions, and resolveDispatcher is the function every hook, useEffect included, calls first to find the dispatcher currently installed by the reconciler. 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, and ElementType are Flow/TypeScript interfaces, not functions: the shared vocabulary the reconciler, DevTools, and Flight 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. setProperty is a smaller case: it lives in the React Compiler's fixture-testing shared runtime (compiler/packages/snap/src/sprout/shared-runtime.ts), a helper library the compiler's own test fixtures share, this one recording writes for the compiler's mutation-tracking assertions, real infrastructure even though its callers are fixtures.

Key subsystems

The Fiber reconciler

2,686 symbols, hub __DEV__ (a development-mode flag referenced from nearly every file in the cluster, not a function; the reconciler's real anchors are Fiber and FiberRoot). This is the scheduler and commit engine every renderer shares: it walks a tree of Fiber nodes rooted at a FiberRoot, assigns each unit of work a priority Lane, and calls into the core react package 327 times, the reverse direction of the heaviest edge on the whole map (see below).

The core react package

1,172 symbols, hub ReactContext. This cluster holds the type definitions and dispatcher-resolution logic every hook goes through, and it calls into the Fiber reconciler 477 times, the single heaviest cross-module edge in the diagram: nearly every context read, dispatcher resolution, and hook call in this cluster eventually reaches into reconciler state.

React Compiler: HIR and source tracking

GeneratedSource, 2,061 symbols. The compiler's high-level intermediate representation: SourceLocation, Place, Identifier, BlockId, and InstructionId are the pieces every lowering pass threads through, calling into react_compiler_ast (7) and HIR (5) as it moves from parsed AST to memoizable instructions.

React Compiler: fixture corpus

compiler, 719 symbols, hub identity. This cluster is the React Compiler's own test-fixture mass: small sample React components (makeObject_Primitives, useIdentity, makeArray, useHook) the compiler's snapshot tests compile and re-compile to check the transform's output, not part of the compiler itself. Its hub, identity, is a one-line pass-through helper from the fixture-testing shared runtime, which is why it is trimmed from the load-bearing list above rather than linked.

React DevTools

Three of the ten largest clusters belong to DevTools: ReactCallSite (1,210 symbols, call-site and element-inspection types), ElementType (920 symbols, the backend/frontend bridge's element-kind and host-instance types), and Rect (627 symbols, the profiler timeline's geometry: Point, Size, View, TimelineData). ElementType and ReactCallSite call into each other more than any other pair in DevTools (119 and 110 calls respectively), and Rect calls into ReactCallSite 24 times, the timeline handing coordinates to the call-site inspector that renders them.

Devtools theme and release scripts

theme, 595 symbols, hub theme. A genuinely mixed cluster: alongside DevTools' UI theme constants sit exec, argv, statSync, and unlinkSync, the release and build scripts that happen to share enough call traffic with the theme module to cluster together. It calls into GeneratedSource (8) and cloneAst (5), which is the compiler-tooling side of this cluster surfacing rather than the UI-theme side.

Flight and Server Components

ReactComponentInfo (1,089 symbols, hub ReactComponentInfo) carries the Flight protocol's component and request metadata: ReactDebugInfo, Thenable, ReactClientValue. react-client (220 symbols, hub Response) is the client-side chunk resolver; its reportGlobalError is where a broken stream rejects every chunk still pending. Together they call into the Fiber reconciler 176 and 59 times respectively, the point where a resolved server payload becomes fiber updates on the client.

react-dom events and client

Three smaller clusters cover DOM event dispatch: legacy-events (238 symbols, hub Instance) and events (84 symbols, hub root) are the plugin-based event system react-dom uses to dispatch native DOM events, calling into the reconciler 59 and 42 times. client (59 symbols, hub SuspendedState) tracks stylesheet-loading state for view transitions. ReactDOMSharedInternals (203 symbols) is the internals object all of them reach through, calling into the reconciler 123 times.

Element creation

createElement, 527 symbols, hub createElement, the JSX element factory every <Component /> compiles down to (or, with the compiler enabled, the memoized call it wraps). Its cluster calls into the core react package (4) and ReactDOMSharedInternals (4); most of its cross-module traffic runs to and from the excluded __tests__ cluster (44 calls out, 38 in), fixture trees exercising the element factory.

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.

  1. useEffect is the public hook. In development it warns if the effect callback is missing, then calls resolveDispatcher() and delegates to dispatcher.useEffect(create, deps).
  2. resolveDispatcher reads ReactSharedInternals.H. Every other hook in the file (useState, useContext, useRef, and the rest) calls the same function first. If H is null, development mode logs the "Invalid hook call" warning readers of the React docs will recognize, then returns the dispatcher regardless (a null dereference is the actual failure mode outside render).
  3. ReactSharedInternals is the module-level singleton whose H field points at whichever hooks dispatcher is currently active.
  4. That field is set by renderWithHooks in the Fiber reconciler, immediately before it calls the function component being rendered, and reset to null immediately after. It installs one of two concrete implementations of the Dispatcher interface, a mount dispatcher on a component's first render and an update dispatcher on every render after, which is why the same useEffect call allocates a new hook on mount but reuses its slot on update.

H being non-null only during that window is why calling a hook outside a function component's render hits the exact warning resolveDispatcher logs: the dispatcher it needs has already been reset to null.

Health signals

Symvanta detected 17 dependency cycles across 365 modules (modularity Q=0.84). The largest spans 214 files in the Components area: React DevTools' view layer (DevTools.js, InspectedElementStateTree.js, WhatChanged.js, ErrorBoundary/TimeoutView.js, and roughly 210 more), where panel components reference the views they render and the views reference the panels that host them. The second-largest, ReactiveScopes (116 files), sits 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. Neither cycle is a defect so much as the expected shape of a UI layer and a multi-pass compiler.

Symvanta also detected 96 sets of mutually recursive symbols, the largest being react-reconciler (193 symbols, including attemptSynchronousHydration, completeRoot, commitPassiveUnmountOnFiber, and flushSyncWorkOnAllRoots): the fiber-tree walkers and commit-phase functions that call each other while walking a tree top-down to render and bottom-up to commit. Flood (68 symbols) and HIR (51 symbols), both from the React Compiler's type system and instruction-lowering code, are the next-largest groups: structurally recursive Flow types (FlowType, ObjType, PropertyMap) and structurally recursive lowering functions naturally call themselves on the nested pieces they contain. A modularity Q of 0.84 across 365 modules indicates most of React's call traffic stays inside its own cluster despite the monorepo housing five separate products; the cycles above are concentrated in the two places a UI layer and a compiler are expected to have them, not spread across the codebase.

See your own codebase mapped like this.

Book a demo →

Auto-generated by Symvanta from the public repo facebook/react at commit 95f4603 , licensed MIT .

Machine-readable companion: data.json (module counts, subsystems, load-bearing symbols, health signals).

Get this for your codebase →