Careeroza
React Context
A Cart Context
Context shares state with a subtree without passing props. In the App Router, the provider is a Client Component placed in a layout.
// components/cart/CartProvider.tsx
'use client';
import { createContext, useContext, useState } from 'react';
type CartItem = { productId: number; qty: number };
type CartCtx = { items: CartItem[]; add: (id: number) => void; count: number };
const CartContext = createContext<CartCtx | null>(null);
export function CartProvider({ children, initial }: { children: React.ReactNode; initial: CartItem[] }) {
const [items, setItems] = useState(initial);
const add = (productId: number) =>
setItems((prev) => {
const found = prev.find((i) => i.productId === productId);
return found
? prev.map((i) => (i.productId === productId ? { ...i, qty: i.qty + 1 } : i))
: [...prev, { productId, qty: 1 }];
});
const count = items.reduce((n, i) => n + i.qty, 0);
return <CartContext.Provider value={{ items, add, count }}>{children}</CartContext.Provider>;
}
export function useCart() {
const ctx = useContext(CartContext);
if (!ctx) throw new Error('useCart must be used inside <CartProvider>');
return ctx;
}Using It
// app/layout.tsx (server): load the cart, pass it as initial state
// <CartProvider initial={await getCartItems()}>{children}</CartProvider>
// components/CartBadge.tsx
'use client';
import { useCart } from './cart/CartProvider';
export default function CartBadge() {
const { count } = useCart();
return <span className="badge">{count}</span>;
}Note: Context re-renders every consumer when the value changes. Fine for theme, user and small carts; for frequent updates prefer Zustand with selectors.