Combining useReducer and context turns local reducer state into state any component below can read and update. The reducer still owns all update logic, while context removes the prop drilling between distant parts of the tree. The result is one provider that manages the state and exposes it to the whole subtree.
Create two contexts
Create one context for the value and a second context for the dispatch function. Export both from a separate file so consumers import the same objects.
import { createContext } from "react";
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);Passing null as the default means a consumer that reads without a provider gets null instead of crashing on undefined. The real values come from the provider you build next.
Why two contexts instead of one
Splitting state and dispatch matters for re-renders. A component that only needs to dispatch does not care when the tasks change, so it should not re-render with every update. Two contexts let a reader subscribe to the value while a writer only subscribes to the dispatch function.
Provide state and dispatch
The reducer itself does not change when you add context. It still takes the current state and an action, then returns the next state. The contexts, reducer, provider, and hooks can all live in one TasksContext file.
function tasksReducer(tasks, action) {
switch (action.type) {
case "added": return [...tasks, { id: action.id, text: action.text }];
case "removed": return tasks.filter((task) => task.id !== action.id);
default: return tasks;
}
}A provider component calls useReducer and passes the state and dispatch into the two contexts, with children rendered in between.
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
return (
<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>
{children}
</TasksDispatchContext>
</TasksContext>
);
}In React 19 you render the context object directly as a provider, as shown. Older versions use TasksContext.Provider with the same value prop. The provider is the only component that calls useReducer, so the tasks state has one clear owner.
Read and update from anywhere
Any component below the provider reads what it needs with useContext. A button that adds a task only needs the dispatch function, so it ignores the tasks list entirely. A list component would read TasksContext and never import dispatch.
import { useContext } from "react";
function AddTask() {
const dispatch = useContext(TasksDispatchContext);
function add() {
dispatch({ type: "added", id: 3, text: "Pack" });
}
return <button onClick={add}>Add task</button>;
}Clicking the button dispatches through context. The reducer in the provider updates the tasks, and only components that read TasksContext re-render. The AddTask component subscribes only to the dispatch context, so a change to the list leaves it untouched.
Hide the wiring behind custom hooks
Export small hooks that wrap each useContext call. Consumers then call useTasks or useTasksDispatch instead of importing the context objects directly.
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}These hooks change no behavior, but they keep the context objects private and give you one place to add checks later, such as throwing when a provider is missing. See create, provide, and read context for the context fundamentals, and useReducer explained with examples for the reducer half.
Rune AI
Key Insights
- useReducer keeps state and dispatch in one provider.
- Context removes prop drilling for distant components.
- Split state and dispatch into two contexts.
- Export custom hooks to read them safely.
- The state still lives in the provider, not in context.
Frequently Asked Questions
Do I need two separate contexts?
Where does the state live when I combine them?
Can I use one context for both state and dispatch?
Conclusion
Combining useReducer and context keeps all update logic in one reducer while letting any component read or dispatch without prop drilling. Create two contexts, provide them from one provider, and expose the result through small custom hooks.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.