javascript 65 lines · 10 steps

Building a shopping cart with Context in React

A reducer holds cart state while Context and a custom hook share it across the component tree.

Explained by highlit
1import { createContext, useContext, useReducer } from 'react';
2 
3const CartContext = createContext(null);
4 
5function cartReducer(state, action) {
6 switch (action.type) {
7 case 'ADD_ITEM': {
8 const existing = state.items.find((i) => i.id === action.product.id);
9 if (existing) {
10 return {
11 items: state.items.map((i) =>
12 i.id === action.product.id
13 ? { ...i, quantity: i.quantity + 1 }
14 : i
15 ),
16 };
17 }
18 return {
19 items: [...state.items, { ...action.product, quantity: 1 }],
20 };
21 }
22 case 'REMOVE_ITEM':
23 return {
24 items: state.items.filter((i) => i.id !== action.id),
25 };
26 case 'SET_QUANTITY': {
27 if (action.quantity <= 0) {
28 return { items: state.items.filter((i) => i.id !== action.id) };
29 }
30 return {
31 items: state.items.map((i) =>
32 i.id === action.id ? { ...i, quantity: action.quantity } : i
33 ),
34 };
35 }
36 case 'CLEAR':
37 return { items: [] };
38 default:
39 return state;
40 }
41}
42 
43export function CartProvider({ children }) {
44 const [state, dispatch] = useReducer(cartReducer, { items: [] });
45 
46 const value = {
47 items: state.items,
48 total: state.items.reduce((sum, i) => sum + i.price * i.quantity, 0),
49 addItem: (product) => dispatch({ type: 'ADD_ITEM', product }),
50 removeItem: (id) => dispatch({ type: 'REMOVE_ITEM', id }),
51 setQuantity: (id, quantity) =>
52 dispatch({ type: 'SET_QUANTITY', id, quantity }),
53 clear: () => dispatch({ type: 'CLEAR' }),
54 };
55 
56 return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
57}
58 
59export function useCart() {
60 const context = useContext(CartContext);
61 if (!context) {
62 throw new Error('useCart must be used within a CartProvider');
63 }
64 return context;
65}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A reducer centralizes all state transitions so every mutation flows through one predictable switch statement.
  2. 2Returning fresh objects and arrays instead of mutating keeps React's change detection reliable.
  3. 3Wrapping useContext in a guarded custom hook enforces correct usage and gives consumers a clean API.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a shopping cart with Context in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code