Skip to content
Personal Github

Cart with Zustand

-4 minutes of reading.

Creando el estado global

Your store is a hook! You can put anything in it: primitives, objects, functions. The set function merges state.

src/store/cart-store.ts

// interfaz exclusiva del producto en el carrito,
// porque no voy a querer almacenar toda la informacion
interface CartProduct {
   id: string;
   slug: string;
   title: string;
   price: number;
   quantity: number;
   size: Size;
   image: string;
}
// interfaz del estado global
interface CartState {
   // interfaz del carrito de compras (un arreglo de la interfaz definida anteriormente)
   cart: CartProduct[];
   // funcion de agregar un producto al carrito
   addProductToCart: (product: CartProduct) => void;
   // funcion para obtener la cantidad total diferente de productos
   // (no contando la cantidad de cada uno)
   getTotalItems: () => number;
   // funcion para simplemente quitar un producto del carrito
   removeProduct: (product: CartProduct) => void;
   // funcion para limpiar el carrito y dejarlo como un arreglo vacio
   clearCart: () => void;
   // funcion para actualizar la cantidad de un producto
   updateProductQuantity: (product: CartProduct, quantity: number) => void;
   getSummaryInformation: () => {
      subTotal: number;
      tax: number;
      itemsInCart: number;
      totalPrice: number;
   };
}

export const useCartStore = create<State>()(
   // persist nos permite almacenarlo en local storage de una manera sencilla
   persist(
      (set, get) => ({
         cart: [],
         addProductToCart: (product: CartProduct) => {
            // obtenemos el estado actual del carrito
            const { cart } = get();
            // segun el id del producto que recibimos, buscamos un boolean
            // que nos diga si ya tenemos algun producto en el carrito
            const productInCart = cart.some(
               (item) => item.id === product.id && item.size === product.size
            );
            // si no lo tenemos simplemente lo agregamos
            if (!productInCart) {
               set({ cart: [...cart, product] });
               return;
            }
            // si lo tenemos nos encargamos de recorrer el carrito mediante un map
            // y cuando encontremos el id y el tamanio del producto aumentamos la cantidad
            const updatedCartProducts = cart.map((item) => {
               if (item.id === product.id && item.size === product.size) {
                  return {
                     ...item,
                     quantity: item.quantity + product.quantity,
                  };
               }
               return item;
            });
            // seteamos el carrito con el carrito actualizado
            set({ cart: updatedCartProducts });
         },
         removeProduct: (product: CartProduct) => {
            // obtenemos el estado actual del carrito
            const { cart } = get();
            // creamos un nuevo arreglo sin el producto que recibimos
            // este nuevo arreglo contiene todos los productos anterioress
            // salvo los que coincidan con el id y el tamanio del producto que recibimos como parametro
            const updatedCartProducts = cart.filter(
               (item) => item.id !== product.id || item.size !== product.size
            );
            // seteamos el carrito con el carrito actualizado
            set({ cart: updatedCartProducts });
         },
         clearCart: () => {
            // simplemente seteamos el carrito como un arreglo vacio
            set({ cart: [] });
         },
         getTotalItems: () => {
            // obtenemos el estado actual del carrito
            const { cart } = get();
            return cart.reduce((total, item) => total + item.quantity, 0);
         },
         updateProductQuantity: (product: CartProduct, quantity: number) => {
            // obtenemos el estado actual del carrito
            const { cart } = get();
            const updatedCartProducts = cart.map((item) => {
               if (item.id === product.id && item.size === product.size) {
                  return {
                     ...item,
                     quantity: quantity,
                  };
               }
               return item;
            });
            // seteamos el carrito con el carrito actualizado
            set({ cart: updatedCartProducts });
         },
         getSummaryInformation: () => {
            // obtenemos el estado actual del carrito
            const { cart } = get();

            const subTotal = cart.reduce(
               (subTotal, product) =>
                  product.quantity * product.price + subTotal,
               0
            );
            const tax = subTotal * 0.15;
            const totalPrice = subTotal + tax;
            const itemsInCart = cart.reduce(
               (total, item) => total + item.quantity,
               0
            );
            return { subTotal, tax, itemsInCart, totalPrice };
         },
      }),
      {
         name: "shopping-cart",
      }
   )
);

Utilizando el estado en la aplicacion

You can use the hook anywhere, without the need of providers. Select your state and the consuming component will re-render when that state changes.

src/App.tsx

const totalItemsInCart = useCartStore((state) => state.getTotalItems());
const { itemsInCart, subTotal, tax, totalPrice } = useCartStore((state) =>
   state.getSummaryInformation()
);
const productsInCart = useCartStore((state) => state.cart);