React useReducer
`useReducer` manages component state by sending actions to a reducer function that calculates the next state. It is especially useful when state transitions are complex, related, or easier to describe as explicit events.
`useReducer` is a React Hook that accepts a reducer and an initial state, returning the current state and a `dispatch` function. Calling `dispatch(action)` asks React to calculate the next state by invoking the reducer with the current state and action.
A reducer is a pure function with the shape `(state, action) => nextState`. The action describes what happened, while the reducer contains the rules for how state changes. A reducer should not mutate the existing state or perform side effects such as network requests.
`useReducer` is often clearer than multiple `useState` calls when several state values change together or when transitions depend on explicit events. Actions can also make state transitions easier to test because the same state and action should produce the same result.
The `dispatch` function does not directly specify the new state. Instead, it sends an action to the reducer, and React schedules the resulting state update. The action can contain additional data, commonly in a `payload` field, but its structure is application-defined.
Key points
- `useReducer` returns `[state, dispatch]`.
- A reducer computes the next state from the current state and an action.
- Reducers should be pure and should not mutate existing state.
- `dispatch` sends an action rather than directly assigning state.
- It is useful for complex or highly related state transitions.
Interview questions
What happens when you call `dispatch` during a render, and why is that generally a problem?
Dispatching during rendering can trigger another state update while React is still calculating the current render. That can cause repeated renders or an infinite update loop when the dispatched state keeps causing the same condition. State transitions should normally be initiated by event handlers, effects when appropriate, or other controlled application logic rather than unconditionally during render. The reducer itself should remain pure so React can safely evaluate state transitions.
What problem does `useReducer` solve compared with `useState`?
`useReducer` is useful when state has several related transitions or when the update logic is becoming difficult to organize with multiple `useState` setters. Instead of describing each update inline, components dispatch actions and a reducer decides the resulting state. This makes the possible transitions explicit and keeps them in one place. For simple independent values, `useState` is often more direct.
What are the inputs and outputs of a reducer?
A reducer receives the current state and an action. Its job is to return the next state, so its conceptual signature is `(state, action) => nextState`. The action identifies what happened and may carry data needed for the transition. A reducer should be deterministic and free of side effects.
Why should a reducer avoid mutating state?
React state should be treated as immutable from the component's perspective. A reducer should create and return a new state value when a transition changes state rather than modifying the existing object or array in place. Immutable updates make state transitions easier to reason about and preserve reliable identity comparisons. For nested data, the changed objects and arrays generally need to be recreated at the appropriate levels.
How would you structure actions for a complex form managed by `useReducer`?
I would model actions around meaningful events such as `fieldChanged`, `submitted`, `submitSucceeded`, and `submitFailed`. Each action would carry only the data needed for that transition, while the reducer would own the rules for changing form state. This keeps event handling separate from state-transition logic. I would also keep asynchronous work outside the reducer and dispatch actions when asynchronous operations complete.