This document describes the React Hooks System implementation, including the dispatcher pattern, hook data structures, effect execution lifecycle, and debugging capabilities. Hooks enable function components to use state and lifecycle features without writing classes.
For information about the overall reconciliation process that invokes hooks, see page 4.2 (Work Loop and Rendering Phases) and page 4.1 (Fiber Architecture and Data Structures). For server-side rendering specifics, see page 5.1 (React Fizz Streaming SSR). For static analysis of hook usage, see page 8.3 (ESLint Plugin for React Hooks).
The hooks system uses a dispatcher pattern to provide different implementations based on component lifecycle phase. During render, hooks resolve to mount, update, or rerender dispatchers. Hook state is stored as a linked list on fiber.memoizedState, while effects form a circular list on fiber.updateQueue.lastEffect.
Hooks System Architecture
Sources: packages/react-reconciler/src/ReactFiberHooks.js1-200 packages/react/src/ReactHooks.js1-242
The public API is exposed through packages/react/src/ReactHooks.js. Each hook function calls resolveDispatcher() to obtain the current dispatcher from ReactSharedInternals.H, then delegates to the dispatcher implementation.
Available Hooks
| Hook | Purpose | Stateful |
|---|---|---|
useState | Local component state | Yes |
useReducer | State with reducer logic | Yes |
useEffect | Side effects after paint | No |
useLayoutEffect | Side effects before paint | No |
useInsertionEffect | CSS-in-JS insertions | No |
useContext | Read context value | No |
useRef | Mutable reference | Yes |
useMemo | Memoized value | Yes |
useCallback | Memoized callback | Yes |
useImperativeHandle | Customize ref value | No |
useDebugValue | DevTools label | No |
useId | Stable unique ID | Yes |
useTransition | Non-blocking updates | Yes |
useDeferredValue | Defer value updates | Yes |
useSyncExternalStore | Subscribe to external store | Yes |
useOptimistic | Optimistic UI updates | Yes |
useActionState | Form action state | Yes |
useEffectEvent | Stable event handler (experimental) | No |
useCacheRefresh | Invalidate cache boundary (experimental) | No |
use | Read Promise/Context | No |
useEffectEvent and useCacheRefresh are listed in HookType in packages/react-reconciler/src/ReactInternalTypes.js46-66 and exposed through the Dispatcher interface at packages/react-reconciler/src/ReactInternalTypes.js397-457
Dispatcher Resolution Flow
Sources: packages/react/src/ReactHooks.js24-42 packages/react/src/ReactHooks.js66-71 packages/react-reconciler/src/ReactInternalTypes.js397-457
The dispatcher is a dynamic vtable stored in ReactSharedInternals.H that changes based on component lifecycle phase. renderWithHooks() sets the appropriate dispatcher before calling the component function, ensuring hooks resolve to the correct implementation. After render, finishRenderingHooks() resets the dispatcher to ContextOnlyDispatcher to prevent hooks from being called outside of render.
Dispatcher Implementations
| Dispatcher | When Used | Behavior |
|---|---|---|
HooksDispatcherOnMount | First render (current === null or memoizedState === null) | Calls mountWorkInProgressHook(), creates linked list |
HooksDispatcherOnUpdate | Subsequent renders | Calls updateWorkInProgressHook(), processes update queues |
HooksDispatcherOnRerender | Render phase updates (setState during render) | Handles re-render loop via renderWithHooksAgain() |
ContextOnlyDispatcher | Outside component render | All hooks throw throwInvalidHookError() |
HooksDispatcherOnMountInDEV | DEV first render | Wraps mount dispatcher, records hookTypesDev |
HooksDispatcherOnUpdateInDEV | DEV subsequent render | Wraps update dispatcher, validates hook order |
HooksDispatcherOnMountWithHookTypesInDEV | DEV update with no stateful hooks | Handles edge case where memoizedState === null on update |
HooksDispatcherOnRerenderInDEV | DEV re-render pass | Wraps rerender dispatcher with DEV validation |
| </old_str> | ||
| <new_str> | ||
The dispatcher is a dynamic vtable stored in ReactSharedInternals.H that changes based on component lifecycle phase. renderWithHooks() sets the appropriate dispatcher before calling the component function, ensuring hooks resolve to the correct implementation. After render, finishRenderingHooks() resets the dispatcher to ContextOnlyDispatcher to prevent hooks from being called outside of render. |
Dispatcher Implementations
| Dispatcher | When Used | Behavior |
|---|---|---|
HooksDispatcherOnMount | First render (current === null or memoizedState === null) | Calls mountWorkInProgressHook(), creates linked list |
HooksDispatcherOnUpdate | Subsequent renders | Calls updateWorkInProgressHook(), processes update queues |
HooksDispatcherOnRerender | Render phase updates (setState during render) | Used during renderWithHooksAgain() re-render loop |
ContextOnlyDispatcher | Outside component render | All hooks call throwInvalidHookError() |
HooksDispatcherOnMountInDEV | DEV first render | Wraps mount dispatcher, records hookTypesDev list |
HooksDispatcherOnUpdateInDEV | DEV subsequent render | Wraps update dispatcher, validates hook order via updateHookTypesDev() |
HooksDispatcherOnMountWithHookTypesInDEV | DEV update with no stateful hooks | Edge case: memoizedState === null but hookTypesDev !== null |
HooksDispatcherOnRerenderInDEV | DEV re-render pass | Wraps rerender dispatcher with DEV validation |
renderWithHooks Execution Flow
Sources: packages/react-reconciler/src/ReactFiberHooks.js502-631 packages/react-reconciler/src/ReactFiberHooks.js633-749 packages/react-reconciler/src/ReactFiberHooks.js787-853
Each hook call appends a Hook object to a singly-linked list stored on fiber.memoizedState. The list order must remain constant across renders (enforced by DEV warnings).
Hook Object Structure
The Hook type is defined in ReactFiberHooks.js. Fields:
| Field | Type | Purpose |
|---|---|---|
memoizedState | any | Current committed state value, or effect object for effect hooks, or [value, deps] for useMemo/useCallback |
baseState | any | State value before any skipped (lower-priority) updates were applied |
baseQueue | Update | null | Circular list of updates that were skipped due to insufficient lane priority |
queue | UpdateQueue | null | Pending update queue for useState/useReducer; null for effect/ref hooks |
next | Hook | null | Next hook in the linked list |
mountWorkInProgressHook() creates a new Hook and appends it to the list. updateWorkInProgressHook() either reuses an existing work-in-progress hook or clones from the current fiber's hook list.
Sources: packages/react-reconciler/src/ReactFiberHooks.js194-200 packages/react-reconciler/src/ReactFiberHooks.js979-998 packages/react-reconciler/src/ReactFiberHooks.js1000-1069
State hooks (useState, useReducer) maintain an UpdateQueue to track pending updates. Updates form a circular linked list and are processed in order during render.
UpdateQueue and Update Structure
Sources: packages/react-reconciler/src/ReactFiberHooks.js164-181
Effects (useEffect, useLayoutEffect, useInsertionEffect) are stored in a circular linked list on fiber.updateQueue.lastEffect. Each Effect object contains the effect function, cleanup function, dependencies, and flags.
Effect and UpdateQueue Data Structures
| Type | Field | Purpose |
|---|---|---|
Effect | tag: HookFlags | Bitfield: HookPassive, HookLayout, HookInsertion, HookHasEffect |
Effect | inst: EffectInstance | Shared object { destroy: void | (() => void) } persisted across renders |
Effect | create | The user-supplied effect callback |
Effect | deps | Dependency array (null means always run) |
Effect | next | Next effect in the circular list |
FunctionComponentUpdateQueue | lastEffect | Tail of the circular effect list (lastEffect.next is the head) |
FunctionComponentUpdateQueue | events | useEffectEvent stable refs |
FunctionComponentUpdateQueue | stores | useSyncExternalStore consistency snapshots |
FunctionComponentUpdateQueue | memoCache | React Compiler's useMemoCache storage |
The lastEffect pointer points to the last effect in the circular list, so lastEffect.next is the first effect. The EffectInstance object (inst) is shared between the mount and subsequent updates to allow the cleanup function to be stored separately from the effect's other fields.
Sources: packages/react-reconciler/src/ReactFiberHooks.js202-227 packages/react-reconciler/src/ReactFiberHooks.js246-252
useState is implemented as a specialized useReducer with a basic state reducer. Both follow the mount/update pattern.
useState/useReducer Implementation Flow
Eager State Computation
When setState is called, React attempts to compute the new state eagerly (outside render) if the queue is empty. If the new state equals the current state (using Object.is), React can bail out of the render entirely.
The dispatchSetState function (for useState) and dispatchReducerAction function (for useReducer) handle all state updates triggered outside of render.
Sources: packages/react-reconciler/src/ReactFiberHooks.js1094-1250
Effect hooks create Effect objects and append them to the circular effect list. The tag field determines when the effect is executed:
HookInsertion: During commit before mutations (for CSS-in-JS)HookLayout: During commit after mutations, before paint (sync)HookPassive: After commit and paint (async via scheduler)Effect Mount and Update
The HookHasEffect flag is the key signal: effects only run during commit if (effect.tag & HookHasEffect) !== 0. On mount all effects have it set; on update only effects with changed deps get it.
Sources: packages/react-reconciler/src/ReactFiberHooks.js1955-2070
useRef is a simple hook that stores a mutable object with a current property. The ref object identity remains stable across renders.
Sources: packages/react-reconciler/src/ReactFiberHooks.js1762-1779
useMemo and useCallback cache computed values based on dependency arrays. During mount, they compute and store the value. During update, they check dependencies and either reuse or recompute.
useMemo Implementation
useCallback(fn, deps) stores [fn, deps] in memoizedState and returns the cached fn when deps are unchanged, avoiding the overhead of calling a factory function.
Sources: packages/react-reconciler/src/ReactFiberHooks.js1888-1955
Effects are executed during the commit phase, but at different times based on their type. The fiber flags (PassiveEffect, UpdateEffect) indicate which effects need to be processed.
Effect Execution Timeline
Effect Tag Bits
| Tag | Constant | Meaning |
|---|---|---|
HookHasEffect | 0b001 | Effect should fire this render |
HookInsertion | 0b010 | Insertion effect (CSS-in-JS) |
HookLayout | 0b100 | Layout effect (sync after mutations) |
HookPassive | 0b1000 | Passive effect (async after paint) |
Effects only execute if effect.tag & HookHasEffect !== 0. During mount, all effects have HookHasEffect. During update, only effects with changed dependencies have HookHasEffect.
Sources: packages/react-reconciler/src/ReactHookEffectTags.js1-21 packages/react-reconciler/src/ReactFiberHooks.js94-99
The Fizz server renderer (packages/react-server/src/ReactFizzHooks.js) provides a separate hooks implementation optimized for synchronous server rendering. Key differences:
useEffect, useLayoutEffect, useInsertionEffect are no-opsrenderPhaseUpdates MapServer Hook State Management
Server useActionState Hook
useActionState (formerly useFormState) is special on the server. It receives permalink information and action state from the request, enabling progressive enhancement of forms. The server implementation uses actionStateCounter and actionStateMatchingIndex module-level counters (set in prepareToUseHooks) to match the correct form state during multi-page navigation, which are read back through getActionStateCount() and getActionStateMatchingIndex().
Sources: packages/react-server/src/ReactFizzHooks.js206-298 packages/react-server/src/ReactFizzHooks.js665-831
The react-debug-tools package provides inspectHooks() and inspectHooksOfFiber() to extract hook information for DevTools. It uses a custom dispatcher that logs all hook calls.
Hook Inspection Architecture
HooksNode Structure
The debug dispatcher intercepts every hook call, extracts its value from the fiber's hook list, and records it in hookLog. Stack trace parsing determines which hooks are nested inside custom hooks.
Sources: packages/react-debug-tools/src/ReactDebugHooks.js1-300 packages/react-debug-tools/src/ReactDebugHooks.js996-1098
In development builds, React enforces the Rules of Hooks through several mechanisms:
Hook Order Validation
Validated Conditions
| Rule | Function | Error / Warning |
|---|---|---|
| Hook call order | updateHookTypesDev() via warnOnHookMismatchInDev() | "React has detected a change in the order of Hooks" |
| Dependency array type | checkDepsAreArrayDev(deps) | "received a final argument that is not an array" |
| Dependency array length | areHookInputsEqual() | "changed size between renders" |
| Effect callback present | useEffect / useLayoutEffect / useInsertionEffect wrappers in ReactHooks.js | "requires an effect callback" |
| Valid render context | throwInvalidHookError() via ContextOnlyDispatcher | "Invalid hook call. Hooks can only be called inside of the body of a function component." |
| No async client components | warnIfAsyncClientComponent() | "is an async Client Component" |
use() not in try/catch | checkIfUseWrappedInTryCatch() in finishRenderingHooks() | "use was called from inside a try/catch block" |
Sources: packages/react-reconciler/src/ReactFiberHooks.js308-394 packages/react-reconciler/src/ReactFiberHooks.js410-451 packages/react-reconciler/src/ReactFiberHooks.js729-748
Complete Hook Execution Flow
Sources: packages/react-reconciler/src/ReactFiberHooks.js502-977
| File | Purpose |
|---|---|
| packages/react/src/ReactHooks.js | Public hooks API, dispatcher resolution |
| packages/react-reconciler/src/ReactFiberHooks.js | Core hooks implementation for client-side rendering |
| packages/react-reconciler/src/ReactInternalTypes.js46-66 | HookType, Dispatcher, Hook type definitions |
| packages/react-reconciler/src/ReactInternalTypes.js398-459 | Dispatcher interface with all hook methods |
| packages/react-server/src/ReactFizzHooks.js | Server-side hooks implementation for Fizz |
| packages/react-debug-tools/src/ReactDebugHooks.js | Hook inspection for DevTools |
| packages/react-reconciler/src/ReactHookEffectTags.js | Effect type flags and constants |
| packages/react-reconciler/src/ReactFiberCommitWork.js | Effect execution during commit phase |
Refresh this wiki