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>
);
}