Rreact.wiki
React Interview Questions

How does useState work under the hood? What happens when you call it multiple times in one component?

InternalsHard6/21/2026hooksfiberreconciliationstateinternals

`useState` relies on React’s internal hook call order and fiber node linkage—each call appends a new `memoizedState` and `updateQueue` to the current fiber’s `memoizedState` linked list, with strict call-order guarantees enforced by React’s dispatcher.

Short Answer

React implements useState by associating each call with a sequentially allocated slot in the component’s fiber node (fiber.memoizedState), forming a singly linked list of hook objects—so multiple useState calls create distinct, order-dependent state entries tied to the same fiber.

Details

When a functional component renders, React’s reconciler sets currentlyRenderingFiber = workInProgressFiber and initializes renderLanes and dispatcher. Each useState(initialState) call invokes mountState() (on first render) or updateState() (on re-renders), which:

  • Reads from currentlyRenderingFiber.memoizedState (a linked list of Hook objects);
  • For mount: creates a new Hook with memoizedState = lazyInit ? lazyInit(initialState) : initialState, baseState = memoizedState, queue = { pending: null, dispatch: boundDispatch }, and links it to the fiber’s hook chain;
  • For update: walks the hook chain using nextCurrentHook (derived from currentFiber.alternate?.memoizedState) and applies queued updates via processUpdateQueue;
  • The dispatch function (a closure over queue and lane) schedules a new render with enqueueUpdate(fiber, update, lane) and triggers reconciliation.
    Crucially, React does not use closures or variable names—it uses call order to map each useState() to its corresponding Hook node. Violating the Rules of Hooks (e.g., calling conditionally) breaks this index alignment, causing state corruption.

Example

TSX
function Counter() {
  const [count, setCount] = useState(0);     // Hook #0 → fiber.memoizedState
  const [name, setName] = useState('');      // Hook #1 → fiber.memoizedState.next
  const [isOn, toggle] = useState(false);    // Hook #2 → fiber.memoizedState.next.next
  // ...
}

Internally, fiber.memoizedState points to a Hook node containing count’s state and queue; its .next points to the name hook; .next.next points to isOn—all statically ordered at render time.

Bonus

To stand out: explain how concurrent rendering affects this—e.g., during an interleaved render, useState may read from currentFiber.alternate.memoizedState and apply updates from both pending queues (current + work-in-progress). Also mention that ReactCurrentDispatcher.current switches between HooksDispatcherOnMount, HooksDispatcherOnUpdate, and HooksDispatcherOnRerender—enabling different behavior for initial mount vs. update vs. bailout. Bonus insight: useState is not magic—it’s just a thin wrapper around useReducer({ type: 'init' }, initialState), revealing React’s unification of state logic under the reducer abstraction.