Combining useReducer with Context API0%
Combining useReducer with Context API
Beginner12 min read•Updated: Jul 11, 2026
Combining useReducer with Context API
1. The Ultimate Lightweight Global State Pattern
For small applications, useState is sufficient. For massive enterprise applications with dozens of developers, Redux Toolkit or Zustand are common choices.
However, for mid-sized applications, design systems, and modular feature trees, combining useReducer with the Context API provides a complete, scalable state management architecture with zero external dependencies!
Output
[Root Context Provider]
(Holds useReducer state & dispatch)
│
┌───────────────┴───────────────┐
▼ ▼
[useCart() - Reads State] [useCartDispatch() - Sends Actions]
(Catalog, Cart Drawer, Badges) (Add Button, Remove Button, Checkout)
2. Implementing the Combined Cart Store
Let's build a production-grade shopping cart store:
JSX
// src/context/CartContext.jsx
import React, { createContext, useContext, useReducer, useEffect } from 'react';
// 1. Define Separate State and Dispatch Contexts
const CartStateContext = createContext(null);
const CartDispatchContext = createContext(null);
const STORAGE_KEY = 'msk_cart_state';
// 2. Reducer Function
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_TO_CART': {
const exists = state.find(item => item.id === action.payload.id);
if (exists) {
return state.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...state, { ...action.payload, quantity: 1 }];
}
case 'REMOVE_FROM_CART':
return state.filter(item => item.id !== action.payload);
case 'CLEAR_CART':
return [];
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
// 3. Provider Component
export function CartProvider({ children }) {
// Initialize state lazily from localStorage
const [cart, dispatch] = useReducer(cartReducer, [], () => {
try {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
} catch {
return [];
}
});
// Sync to localStorage
useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(cart));
}, [cart]);
return (
<CartStateContext.Provider value={cart}>
<CartDispatchContext.Provider value={dispatch}>
{children}
</CartDispatchContext.Provider>
</CartStateContext.Provider>
);
}
// 4. Custom Consumer Hooks
export function useCart() {
const context = useContext(CartStateContext);
if (!context) {
throw new Error('useCart must be used within a <CartProvider>');
}
return context;
}
export function useCartDispatch() {
const context = useContext(CartDispatchContext);
if (!context) {
throw new Error('useCartDispatch must be used within a <CartProvider>');
}
return context;
}
3. High-Performance Consumer Usage
Component 1: Navbar Badge (Reads State Only)
JSX
import React from 'react';
import { useCart } from '../context/CartContext';
export default function CartBadge() {
const cart = useCart(); // Subscribed to state updates
const totalQuantity = cart.reduce((sum, item) => sum + item.quantity, 0);
return <div className="badge">Cart: {totalQuantity}</div>;
}
Component 2: Add Button (Dispatches Actions Only)
JSX
import React from 'react';
import { useCartDispatch } from '../context/CartContext';
export default function AddToCartButton({ course }) {
const dispatch = useCartDispatch(); // Consumes STABLE dispatch reference!
return (
<button
onClick={() => dispatch({ type: 'ADD_TO_CART', payload: course })}
className="btn-add"
>
Enroll Now (${course.price})
</button>
);
}
Because AddToCartButton only consumes CartDispatchContext, it will NEVER re-render when items are added to the cart!
Interactive Quiz
Practice Quiz
Question 1
What makes combining
useReducer with Context an attractive state management solution?Question 2
Why are two separate contexts (
CartStateContext and CartDispatchContext) created?Question 3
How is initial state lazily loaded in
useReducer(cartReducer, [], initializerFunction)?Question 4
What happens if an unknown action type is dispatched to a reducer that ends with
throw new Error(...)?Question 5
How do consumer components add items to the cart when using this architecture?
Next Lesson
Fetching REST APIs with Async Await and State
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
| Previous Lesson | Next Lesson |
|---|---|
| Managing Complex State Transitions with useReducer | Fetching REST APIs with Async Await and State |
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.