Manage Cart State with Zustand
LAST UPDATED AT: Mar 8, 2025
Zustand, meaning βstateβ in German, is a lightweight state management library for React that offers an intuitive and efficient way to manage application state.
In this blog, I will setup a simple cart application in Zustand . In Zustand, we store the state in a hook rather than a context (used in React-redux).
Let's see the code π
Note: I have used TailwindCSS for styling the UI.
After finishing, this is we will get this:
App.tsx
// Example static food items import FoodItems from './FoodItems'; import ShoppingCart from './ShoppingCart'; import {Food} from './types'; const foodItems:Food[] = [ { id: 1, name: "Burger", description: "Juicy grilled beef burger with fresh veggies", price: 5.99, image: "https://images.unsplash.com/photo-1568901346375-23c9450c58cd?q=80&w=2799&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", }, { id: 2, name: "Pizza", description: "Cheesy pizza topped with fresh tomatoes and basil", price: 8.99, image: "https://images.unsplash.com/photo-1513104890138-7c749659a591?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", }, { id: 3, name: "Salad", description: "Fresh mixed greens with a tangy vinaigrette", price: 4.99, image: "https://plus.unsplash.com/premium_photo-1701699257759-4189d01f4351?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", }, { id: 4, name: "Pasta", description: "Creamy Alfredo pasta with grilled chicken", price: 7.99, image: "https://images.unsplash.com/photo-1621996346565-e3dbc646d9a9?q=80&w=2706&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", }, ]; const App = ()=> { return ( <div className="flex"> <ShoppingCart /> <FoodItems items={foodItems}/> </div> ) } export default App;
Here, I have added some sample food items. This is passed as props to FoodItems
.We will see how to add the items from FoodItems to ShoppingCart "magically" via Zustand.
cartStore.ts
import { Food } from '@/types'; import {create} from 'zustand'; type Sku = { item: Food, count: number, } type CartStoreState = { cart: Sku[], addToCart: (food: Food)=> void, incrementDecrementSku: (foodId: number, type: 'increment'|'decrement', quantity?: number )=> void, deleteSku: (foodId: number )=> void, clearCart: ()=> void, } export const useCartStore = create<CartStoreState>((set)=>({ cart: [], addToCart: (product)=> set((state)=> { const cart = state.cart; const isSameProductExists = cart.find(sku => sku.item.id === product.id); if(isSameProductExists){ isSameProductExists.count += 1; return {cart:[...cart]}; } return {cart: [...state.cart, {item: product, count: 1}] } }) , incrementDecrementSku:(productId, type, quantity = 1)=> set(state=> { const cart = state.cart; const productIndex = cart.findIndex(sku => sku.item.id === productId); if(productIndex !== -1){ const updatedCart = [...cart]; const currentSku = updatedCart[productIndex]; if(type === 'decrement'){ if(currentSku.count <= quantity){ updatedCart.splice(productIndex,1); // Remove the product if count reaches 0 or below }else{ currentSku.count -= quantity; } }else if(type === 'increment'){ currentSku.count += quantity; } return {cart:updatedCart}; } return {cart: cart } }), deleteSku:(id)=> set((state)=> ({cart: state.cart.filter(sku=> sku.item.id !== id)})), clearCart:()=> set({cart: []}) }));
The create
function from zustand helps in creating a store .It takes a callback function with the first param which is a setter method 'set' and returns the state.
Here, the state has five items: cart, addToCart, incrementDecrementSku,deleteSku , clearCart .
FoodItems.tsx
import { useCartStore } from './store/cartStore'; import {Food} from './types'; import { Plus } from "lucide-react"; const FoodItems = ({ items }:{items: Food[]}) => { const {addToCart} = useCartStore(); return ( <div className="w-3/4 p-4"> <h1 className="text-2xl font-bold mb-4 text-center">Food Cart</h1> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> {items.map((item) => ( <div key={item.name} className="border p-4 rounded-lg shadow-md flex flex-col items-center" > <img src={item.image} alt={item.name} className="w-32 h-32 object-cover mb-4 rounded" /> <h2 className="text-lg font-semibold mb-2">{item.name}</h2> <p className="text-gray-600 mb-2">{item.description}</p> <p className="text-green-500 font-bold mb-2">${item.price}</p> <button className="p-2 rounded-full bg-blue-500 text-white hover:bg-blue-600" onClick={()=>addToCart(item)}> <Plus className="w-5 h-5" /> </button> </div> ))} </div> </div> ); }; export default FoodItems;
The FoodItems displays the item by looping over them and each item has a add button which calls the addToCart
function of useCartStore.
ShoppingCart.tsx
import { Minus, Plus, ShoppingCart as ShoppingCartIcon } from "lucide-react"; import { useCartStore } from "./store/cartStore"; const ShoppingCart = ()=>{ const {cart, incrementDecrementSku, clearCart, deleteSku} = useCartStore(); return (<div className="w-1/4 p-4 bg-gray-100 shadow-md"> <div className="flex flex-col items-center"> <button className="p-4 rounded-full bg-gray-200 hover:bg-gray-300"> <ShoppingCartIcon className="w-8 h-8 text-gray-700" /> </button> <button className="p-2 rounded-full bg-gray-200 hover:bg-gray-300 mt-1" onClick={clearCart}> Clear All </button> <p className="mt-2 text-gray-700 font-semibold">Your Cart</p> { cart.map((sku)=>( <div key={sku.item.id} className="bg-blue-200 p-2 px-3 rounded-lg m-2 min-w-full"> <div className="flex items-center justify-between gap-2"> {sku.item.name} <QuantityControlButton count={sku.count} decrementBy1={()=> incrementDecrementSku(sku.item.id,'decrement')} incrementBy1={()=> incrementDecrementSku(sku.item.id,'increment')} /> </div> <button className="border border-red-500 p-1 rounded-md" onClick={()=> deleteSku(sku.item.id)}>Delete</button> </div> )) } </div> </div>) } export default ShoppingCart; type QuantityControlButtonProps = { count: number; incrementBy1: ()=> void; decrementBy1: ()=> void; } export const QuantityControlButton = ({count, decrementBy1, incrementBy1}:QuantityControlButtonProps)=>{ return ( <div className="flex items-center justify-between border-2 gap-4 border-yellow-500 p-1 rounded-3xl"> <Minus size={16} onClick={decrementBy1} className="cursor-pointer"></Minus> {count} <Plus size={16} onClick={incrementBy1} className="cursor-pointer"></Plus> </div> ) }
In the ShoppingCart, we are adding/removing/deleting items from the cart.
Overall, Zustand helps us to write state management code in a leaner way compared to React-redux.
Β
chao π