Describe combining useContext and useReducer for scalable feature state

This tests replacing prop drilling with a context-reducer pattern. A strong answer covers two contexts for state and dispatch, a provider wrapping the subtree, and a pure reducer. A red flag is one global context or async logic inside the reducer.
WHAT THIS TESTS: Whether you understand how to scale a React feature beyond local component state without creating a brittle prop-drilling hierarchy or an over-engineered global store. The interviewer wants to see that you can encapsulate state logic inside a single feature boundary, keep state updates predictable and traceable, and expose that state to deeply nested consumers through React's built-in primitives rather than external libraries.
A GOOD ANSWER COVERS: First, create two distinct contexts, one for the current state and one for the dispatch function, so that components calling dispatch do not re-render when state changes. Second, build a provider component that calls useReducer and supplies both values to their respective contexts, placing this provider at the root of the feature subtree. Third, keep the reducer function pure, deterministic, and colocated with the provider or in a dedicated module, handling all state transitions through explicit action types. Fourth, define clear action shapes and optionally use TypeScript or PropTypes so consumers know what payloads are expected. Fifth, consume the contexts with custom hooks that throw if used outside the provider, enforcing correct usage.
COMMON WRONG ANSWERS: Putting the reducer and context in the same file as UI components, which destroys separation of concerns. Using a single context object that contains both state and dispatch, causing every consumer to re-render on every state change. Performing side effects like API calls inside the reducer instead of in event handlers or effects that dispatch afterward. Creating one giant global context for the entire application instead of scoping providers to specific features. Forgetting to memoize the context value object, which breaks shouldComponentUpdate or React.memo optimizations down the tree.
LIKELY FOLLOW-UPS: How would you persist this state to localStorage or sync it with a server? The answer should describe an effect inside the provider or a middleware pattern, not async reducers. How do you prevent excessive re-renders when the state object grows large? The answer should mention splitting contexts, memoizing derived state, or using selectors. Would you reach for Redux or Zustand instead? A strong response compares trade-offs, noting that context plus reducer is excellent for localized medium-frequency updates but can struggle with very high-frequency updates compared to specialized libraries.
ONE CONCRETE EXAMPLE: Imagine a complex Kanban board feature. You would define a BoardProvider that wraps a single project view. Inside BoardProvider, useReducer manages columns, cards, and drag-drop state. Two contexts, BoardStateContext and BoardDispatchContext, are created. A useBoardState hook reads columns and cards, while a useBoardDispatch hook returns the dispatch function. Card components call dispatch with type MOVE_CARD and payload sourceId and targetColumnId. The reducer computes the new column order immutably. No props pass through Column or Card components, and the entire state layer can be deleted or tested independently of the UI.
Source: react.dev
Read the original → react.dev
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.