DEV Community

Cover image for Manage Cart State with Zustand
Abhirup Datta
Abhirup Datta

Posted on

Manage Cart State with Zustand

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:
Output

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;

Enter fullscreen mode Exit fullscreen mode

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: []})
}));

Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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.

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay