Sikau

Project: The café front end · Day 2 · about 40 min

A cart with useReducer and context

Pure reducer for cart updates, shared with any component through context.

Two components, one cart

The menu adds dishes; the summary shows the total. They're far apart in the tree, so put the cart in context, and every change through a reducer — a pure function from the old state and an action to the new state.

Reducer + context

import { createContext, useContext, useReducer } from "react";

const DISHES = [
  { slug: "momo", name: "Momo", price: 180, veg: false },
  { slug: "veg-momo", name: "Veg momo", price: 160, veg: true },
  { slug: "thukpa", name: "Thukpa", price: 220, veg: false },
  { slug: "sel-roti", name: "Sel roti", price: 60, veg: true },
];

function cartReducer(state, action) {
  switch (action.type) {
    case "add":
      return { ...state, [action.slug]: (state[action.slug] ?? 0) + 1 };
    case "remove": {
      const { [action.slug]: qty = 0, ...rest } = state;
      return qty > 1 ? { ...rest, [action.slug]: qty - 1 } : rest;
    }
    case "clear":
      return {};
    default:
      return state;
  }
}

const CartContext = createContext(null);

function CartProvider({ children }) {
  const [cart, dispatch] = useReducer(cartReducer, {});
  return <CartContext.Provider value={{ cart, dispatch }}>{children}</CartContext.Provider>;
}

function useCart() {
  return useContext(CartContext);
}

function Menu() {
  const { dispatch } = useCart();
  return (
    <ul>
      {DISHES.map((d) => (
        <li key={d.slug}>
          {d.name} <button data-slug={d.slug} onClick={() => dispatch({ type: "add", slug: d.slug })}>Add {d.name}</button>
        </li>
      ))}
    </ul>
  );
}

function CartSummary() {
  const { cart, dispatch } = useCart();
  const count = Object.values(cart).reduce((n, q) => n + q, 0);
  const total = Object.entries(cart).reduce((sum, [slug, q]) => sum + DISHES.find((d) => d.slug === slug).price * q, 0);
  return (
    <aside>
      <p id="cart-count">{count} items</p>
      <p id="cart-total">Rs {total}</p>
      <button id="clear" onClick={() => dispatch({ type: "clear" })}>Clear</button>
    </aside>
  );
}

export default function App() {
  return (
    <CartProvider>
      <Menu />
      <CartSummary />
    </CartProvider>
  );
}

The rest of this lesson

  • Designing the cart stateexplain
  • Sharing it with contextexplain
  • Same object?predict
  • Remove the last onepredict
  • Share the cartwrite code
  • Badge quizquiz
  • What you builtrecap

Continue this lesson free — run real code in your browser

Continue with Google

See all 5 lessons of Project: The café front end