Hooks queue and cursor
Each function component has a hooks queue stored on its Fiber node. This queue maintains all hooks called during rendering in a strict order.The hooks queue is a linked list where each node represents a hook call. React uses a cursor to traverse this list during each render.
How the queue works
First render
When a component renders for the first time, React creates a new hook node for each hook call and appends it to the queue.
Subsequent renders
On re-renders, React resets the cursor to the beginning of the queue and walks through it, matching each hook call to its corresponding node.
State retrieval
When a hook is called, React uses the cursor to find the hook node, retrieves its state, and advances the cursor to the next node.
- Hook node structure
- Why order matters
Each hook node in the queue contains:
memoizedState: The current state valuebaseState: The base state before updatesqueue: Pending updates for this hookbaseQueue: Updates that weren’t processednext: Pointer to the next hook node
The cursor pattern
React maintains a global cursor that points to the current hook being processed:This cursor-based approach is why you’ll see errors like “Rendered more hooks than during the previous render” - React detected a mismatch between the queue and the hook calls.
useEffect cleanup and dependency checking
TheuseEffect hook is implemented with a sophisticated system for tracking dependencies, scheduling effects, and managing cleanup functions.
Dependency checking
React compares dependencies usingObject.is (shallow equality) to determine if an effect should run.
Compare on re-render
On subsequent renders, React compares each new dependency with its corresponding previous dependency using
Object.is.Mark for execution
If any dependency changed (or if no dependency array is provided), React marks the effect to run during the commit phase.
- No dependencies
- Empty dependencies
- With dependencies
Cleanup function lifecycle
When an effect returns a cleanup function, React manages its execution carefully:Run before next effect
Before running the effect again (if dependencies changed), React calls the previous cleanup function.
Cleanup functions run synchronously during the commit phase, before the next effect runs. This ensures proper cleanup ordering and prevents memory leaks.
Effect timing
- useEffect
- useLayoutEffect
- Runs asynchronously after the paint
- Does not block the browser from updating the screen
- Scheduled using
requestIdleCallbackorsetTimeout - Best for most side effects (data fetching, subscriptions)
useRef mutable container
TheuseRef hook provides a mutable container that persists across renders without causing re-renders when mutated.
Unlike state, updating a ref’s
current property does not trigger a re-render. The ref object itself remains the same across all renders.Implementation details
useRef is implemented as a simple object with a current property:
Create ref object
On the first render, React creates an object with a
current property set to the initial value.Common use cases
- DOM references
- Previous values
- Mutable values
useCallback and useMemo memoization
The memoization hooksuseCallback and useMemo prevent expensive recalculations by caching results based on dependencies.
useCallback
useCallback returns a memoized version of a callback function that only changes when dependencies change.
Store callback and deps
On the first render, React stores both the callback and dependencies in the hook node.
Compare dependencies
On re-renders, React compares the new dependencies with the cached dependencies.
useCallback is particularly useful for preventing child components from re-rendering when passed as props, especially with React.memo.useMemo
useMemo memoizes the result of an expensive computation:
- useCallback
- useMemo
useMemo(() => fn, deps).When to use memoization
Expensive computations
Use
useMemo for computations that take significant time and are called frequently with the same inputs.Referential equality
Use
useCallback and useMemo when passing objects or functions as dependencies to other hooks, or as props to memoized child components.Remember: memoization trades memory for computation time. The cached values and functions must be stored, and the dependency comparison itself has a cost.
Custom hook composition
Custom hooks allow you to compose built-in hooks into reusable logic. They follow the same implementation rules as built-in hooks.Custom hooks must start with “use” so React’s linter can verify that Rules of Hooks are followed. Under the hood, they’re just functions that call other hooks.