Combining useReducer with Context API0%

Combining useReducer with Context API

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

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.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext