Skip to main content

Overview

The shopping cart is implemented using React Context API for global state management. It provides cart operations, automatic price calculations, and persistent state across the application.

Architecture

1

Context Provider

CarritoContext wraps the entire app and provides cart state
2

Custom Hook

useCarrito hook provides access to cart operations
3

Cart Components

Specialized components for displaying and managing cart items

Context Implementation

CarritoContext Provider

src/context/CarritoContext.jsx
import { createContext, useContext, useState } from "react";

const CarritoContext = createContext();

export const CarritoProvider = ({ children }) => {
  const [items, setItems] = useState([]);

  const agregarAlCarrito = (producto) => {
    setItems(prev => {
      const existe = prev.find(i => i.id === producto.id);
      if (existe) {
        return prev.map(i =>
          i.id === producto.id ? { ...i, cantidad: i.cantidad + 1 } : i
        );
      }
      return [...prev, { ...producto, cantidad: 1 }];
    });
  };

  const updateCantidad = (id, cantidad) => {
    if (cantidad < 1) return;
    setItems(prev => prev.map(i => i.id === id ? { ...i, cantidad } : i));
  };

  const removeItem = (id) =>
    setItems(prev => prev.filter(i => i.id !== id));

  const subtotal = items.reduce((sum, i) => sum + i.precio * i.cantidad, 0);
  const envio    = subtotal > 5000 ? 0 : 800;
  const total    = subtotal + envio;

  return (
    <CarritoContext.Provider value={{
      items,
      agregarAlCarrito,
      updateCantidad,
      removeItem,
      subtotal,
      envio,
      total,
    }}>
      {children}
    </CarritoContext.Provider>
  );
};

export const useCarrito = () => useContext(CarritoContext);
export default useCarrito;
src/context/CarritoContext.jsx

State Structure

Cart Item Object

interface CartItem {
  id: number;           // Product ID
  nombre: string;       // Product name
  precio: number;       // Unit price in ARS
  img?: string;         // Product image URL
  talle?: string;       // Size (optional)
  cantidad: number;     // Quantity in cart
}

Context Value

items
CartItem[]
Array of items currently in the cart
agregarAlCarrito
function
Add a product to the cart or increment quantity if it exists
(producto: Product) => void
updateCantidad
function
Update the quantity of a cart item
(id: number, cantidad: number) => void
removeItem
function
Remove an item from the cart
(id: number) => void
subtotal
number
Sum of all items (price × quantity)
envio
number
Shipping cost (800 ARS or free if subtotal > 5000)
total
number
Final total (subtotal + shipping)

Cart Operations

Adding Items

When adding a product to the cart:
  1. Check if the product already exists in the cart
  2. If it exists, increment the quantity by 1
  3. If it’s new, add it with quantity 1
const agregarAlCarrito = (producto) => {
  setItems(prev => {
    const existe = prev.find(i => i.id === producto.id);
    if (existe) {
      // Increment existing item
      return prev.map(i =>
        i.id === producto.id ? { ...i, cantidad: i.cantidad + 1 } : i
      );
    }
    // Add new item
    return [...prev, { ...producto, cantidad: 1 }];
  });
};
Products with the same ID are treated as the same item, even if they have different attributes. The quantity is simply incremented.

Updating Quantity

Update the quantity of a specific item:
const updateCantidad = (id, cantidad) => {
  if (cantidad < 1) return; // Prevent quantity < 1
  setItems(prev => prev.map(i => i.id === id ? { ...i, cantidad } : i));
};
The minimum quantity is 1. To remove an item, use removeItem instead of setting quantity to 0.

Removing Items

Remove an item completely from the cart:
const removeItem = (id) =>
  setItems(prev => prev.filter(i => i.id !== id));

Price Calculations

Subtotal

Sum of all items (unit price × quantity):
const subtotal = items.reduce((sum, i) => sum + i.precio * i.cantidad, 0);

Shipping Cost

Free shipping for orders over 5000 ARS:
const envio = subtotal > 5000 ? 0 : 800;

Standard Shipping

800 ARS for orders under 5000 ARS

Free Shipping

Free for orders 5000 ARS and above

Total

Final amount including shipping:
const total = subtotal + envio;

Using the Cart Hook

In Components

import useCarrito from "../../hooks/useCarrito";

function ProductCard({ producto }) {
  const { agregarAlCarrito } = useCarrito();

  const handleAdd = () => {
    agregarAlCarrito({
      id: producto.idProducto,
      nombre: producto.nombre,
      precio: producto.precio,
      img: producto.img,
      talle: producto.talle ?? null,
    });
  };

  return (
    <button onClick={handleAdd}>
      Agregar al carrito
    </button>
  );
}

In Cart Page

src/pages/Cart/Carrito.jsx
import useCarrito from "../../hooks/useCarrito";
import CarritoItem from "../../components/Carrito/CarritoItem";
import CarritoResumen from "../../components/Carrito/CarritoResumen";
import CarritoVacio from "../../components/Carrito/CarritoVacio";

export const Carrito = () => {
  const { items, updateCantidad, removeItem, subtotal, envio, total } = useCarrito();

  if (items.length === 0) return <CarritoVacio/>;

  return (
    <div>
      <h1>Mi Carrito</h1>
      <p>{items.length} producto{items.length !== 1 ? "s" : ""} en tu carrito</p>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 360px" }}>
        {/* Lista productos */}
        <div>
          {items.map(item => (
            <CarritoItem
              key={item.id}
              item={item}
              updateCantidad={updateCantidad}
              removeItem={removeItem}
            />
          ))}
        </div>

        {/* Resumen */}
        <CarritoResumen subtotal={subtotal} envio={envio} total={total}/>
      </div>
    </div>
  );
};
src/pages/Cart/Carrito.jsx

Cart Components

CarritoItem

Displays a single cart item with controls:
  • Product image and name
  • Unit price
  • Quantity controls (+/-)
  • Line total (price × quantity)
  • Remove button
See Components for implementation details.

CarritoResumen

Displays order summary:
function CarritoResumen({ subtotal, envio, total }) {
  return (
    <div className="summary-panel">
      <h3>Resumen del pedido</h3>
      
      <div className="summary-row">
        <span>Subtotal</span>
        <span>${subtotal.toLocaleString("es-AR")}</span>
      </div>
      
      <div className="summary-row">
        <span>Envío</span>
        <span>
          {envio === 0 
            ? "Gratis" 
            : `$${envio.toLocaleString("es-AR")}`
          }
        </span>
      </div>
      
      <div className="summary-row total">
        <span>Total</span>
        <span>${total.toLocaleString("es-AR")}</span>
      </div>
      
      <button>Finalizar compra</button>
    </div>
  );
}

CarritoVacio

Empty state component when cart has no items:
function CarritoVacio() {
  return (
    <div className="empty-cart">
      <span style={{ fontSize: "4rem" }}>🛒</span>
      <h2>Tu carrito está vacío</h2>
      <p>Agrega productos para comenzar tu compra</p>
      <Link to="/">
        <button>Ver productos</button>
      </Link>
    </div>
  );
}

State Management Flow

Hook Export Pattern

The useCarrito hook is exported in two ways:
src/context/CarritoContext.jsx
// Named export
export const useCarrito = () => useContext(CarritoContext);

// Default export
export default useCarrito;
And re-exported from a dedicated hooks file:
src/hooks/useCarrito.js
export { useCarrito as default } from "../context/CarritoContext";
This allows both import styles:
// Default import
import useCarrito from "../../hooks/useCarrito";

// Named import
import { useCarrito } from "../../context/CarritoContext";

Future Enhancements

LocalStorage Persistence

Save cart state to localStorage to persist across page refreshes

Cart Badge Counter

Show total item count in the header cart icon

Product Variants

Support different sizes/colors as separate cart items

Discount Codes

Apply promotional codes to the cart total

Adding LocalStorage Persistence

Example implementation for persisting cart state:
import { createContext, useContext, useState, useEffect } from "react";

const CART_STORAGE_KEY = "huellitas_cart";

export const CarritoProvider = ({ children }) => {
  // Initialize from localStorage
  const [items, setItems] = useState(() => {
    const saved = localStorage.getItem(CART_STORAGE_KEY);
    return saved ? JSON.parse(saved) : [];
  });

  // Save to localStorage on every change
  useEffect(() => {
    localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(items));
  }, [items]);

  // ... rest of implementation
};
Be careful with localStorage in SSR environments. Always check if window is defined before accessing localStorage.

Testing the Cart

Manual Testing Checklist

1

Add items

Add multiple products and verify quantity increments for duplicates
2

Update quantities

Use +/- buttons to change quantities and verify price updates
3

Remove items

Delete items and verify they’re removed from the list
4

Check calculations

Verify subtotal, shipping (free over 5000), and total are correct
5

Empty cart

Remove all items and verify empty state is shown

Next Steps

Components

Learn about cart UI components

Backend API

Connect cart to backend checkout

Build docs developers (and LLMs) love