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.
Top comments (0)