React Fiber Reconciler: Schedule to Commit
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. This page is a deep dive into one subsystem of React Architecture: How It Actually Works; the hub page holds the whole-repo module map and glossary.
The Fiber reconciler is the part of React that decides what changed and when to apply it. Symvanta's Louvain community detection groups it into a single cluster, react-reconciler, at 1,980 symbols the largest product cluster in the repository, tied exactly with the React Compiler's HIR. Its hub is Fiber itself. The cluster also absorbs React's shared type files at this commit, so ReactContext, ReactSharedInternals, Lanes, and Wakeable sit in the same community as the fiber walkers that read them. Every renderer React ships (react-dom, react-native-renderer, react-test-renderer, react-art) drives the same reconciler code; only the host config that turns fiber effects into real mutations differs. This is why a single package owns both the scheduler that prioritizes work and the commit engine that writes it out.
The reconciler's job splits cleanly in two. The render phase builds a work-in-progress tree of Fiber nodes and can be paused, aborted, or restarted at a higher priority. The commit phase takes a completed tree and applies it to the host in one synchronous, uninterruptible pass. The data structures below are what make that interruptibility possible: React keeps two copies of every fiber and never mutates the visible tree until the whole render is finished. This analysis is generated from Symvanta's code graph over facebook/react at commit eafeac0. It describes React's internals; for React app architecture in the sense of laying out your own codebase, read the companion post instead.
The moving parts
FiberFiberRootLanescheduleUpdateOnFiberperformWorkOnRootbeginWorkcompleteWorkcommitRootrenderWithHooks
A Fiber is one unit of work: a single object with roughly thirty fields spanning lines 89 to 210, carrying the component type, the stateNode (the host instance or class instance), the return, child, and sibling pointers that form the tree, a memoizedState field (the head of the hook linked list for function components), a lanes bitmask of pending work, flags and subtreeFlags effect bitfields, and an alternate pointer to its paired copy. That alternate is the whole trick: every fiber that updates gets a work-in-progress twin, so React can build the next tree without touching the one currently on screen. The FiberRoot sits above the tree; its current pointer names whichever of the two trees is live, and the swap between them is a single assignment at commit time. A Lane is a single bit in a 31-bit priority set; Lanes is a set of them, and the reconciler uses lane bitmasks everywhere to decide which pending updates a given render should include and which to defer. renderWithHooks is the seam every renderer shares: it installs the hooks dispatcher and calls the component function, the bridge between the reconciler and the hooks dispatcher.
How it works
Here is the canonical path a state update travels, from a component calling setState (a bound dispatchSetState) through the pixels changing on screen.
dispatchSetStateis the functionuseStatereturns, bound to the fiber and its update queue. It asksrequestUpdateLane(fiber)for a priority lane, then hands off todispatchSetStateInternal, which enqueues an update record. When the queue is empty it eagerly computes the next state first and bails out entirely if the value is unchanged, so an unchangedsetStatenever schedules a render.scheduleUpdateOnFiberis where a real update enters the work loop. Thereturn-pointer walk that propagateschildLanesup the tree has already happened by then, inmarkUpdateLaneFromFiberToRoot, which is also how theFiberRootpassed in here was found.scheduleUpdateOnFibercallsmarkRootUpdatedto merge the new lane into the root's pending set, thenensureRootIsScheduled.ensureRootIsScheduledregisters the root with the root scheduler so a task is queued (via the Scheduler package for concurrent work, or a microtask for sync work). Multiple updates in the same tick collapse into one scheduled render here.performWorkOnRootis the entry point the scheduled task calls. It choosesrenderRootSyncorrenderRootConcurrentbased on the lanes and whether the work can be time-sliced, then drives the render phase to completion before finishing with a commit.beginWorkruns on the way down. The work loop (workLoopSyncorworkLoopConcurrent) calls it once per fiber, top-down; it reconciles children against the previous tree, and for a function component it callsrenderWithHooksto run the component body and diff its output into child fibers.completeWorkruns on the way back up, once a fiber has no more children to descend into. It creates or updates the host instance (a DOM node, for react-dom), bubbles each fiber'sflagsinto its parent'ssubtreeFlags, and returns to the sibling or parent, so the loop knows in one bitmask read whether a subtree has any commit work at all.commitRoottakes over once the work-in-progress tree is complete. It runs the before-mutation snapshot, thencommitMutationEffects(which inserts, updates, and deletes host nodes), then flipsroot.currentto the finished tree, thencommitLayoutEffects(which firesuseLayoutEffectand attaches refs against the now-current tree). This whole pass is synchronous and cannot be interrupted.flushPassiveEffectsis the deferred tail. Passive effects (useEffectcleanups and callbacks) are scheduled during commit but run later, off the critical path, which is what makesuseEffectfire after paint whileuseLayoutEffectfires before it.
Where it connects
The reconciler cluster is the hub the rest of React reaches through. On the module map, react-reconciler sits at both ends of the heaviest traffic in the codebase. Its own busiest edge runs into react-api-and-fizz 430 times, the single heaviest cross-module edge on the map: that cluster holds the public react package and the shared type vocabulary the reconciler reads on every path. Next comes the react-dom host layer (react-dom-bindings, hub DOMEventName), which the reconciler calls 217 times and which calls back 229, the seam where fiber effects become real DOM mutations. Inbound after that: the React API and Fizz cluster reaches in 133 times, React DevTools' element-inspection cluster (ElementType) 79, Flight (ReactComponentInfo) 55, the react-native renderer's host types and legacy event plumbing (Component Structure, hub Instance) 29, and the alternate renderers 15 from react-test-renderer and 12 from react-noop-renderer.
The tightest coupling is with hooks. renderWithHooks lives inside this cluster and is the exact point where the reconciler installs a dispatcher and calls a component; the sibling hooks dispatcher spoke picks the story up there, following a single useState call through mount and re-render. Every hook's state, the memoizedState linked list described above, hangs off the very Fiber objects the render phase walks.
By the numbers
Symvanta counts 1,980 symbols in the react-reconciler cluster. The Fiber type alone spans 122 lines (89 to 210) as one flat object, deliberately merged into a single allocation and never split across intersected types. commitRoot runs 198 lines (3,725 to 3,922) of synchronous commit orchestration. The reconciler also forms one of the repository's 15 dependency cycles, a 91-file cycle across the render and commit modules that call back and forth, the third-largest cycle in the codebase behind the React Compiler's reactive-scope passes (116 files) and DevTools' view layer (115). It carries the largest set of mutually recursive symbols too, 142 of them, the walkers that descend into a fiber's children and return through the same functions.
Auto-generated by Symvanta from the public repo facebook/react at commit eafeac0 , licensed MIT .