T

TechIdea

Ecosystem

Back to react Projects
Advanced Level

React E-commerce Cart

Build an advanced E-commerce Shopping Cart in React demonstrating product listing grids, cart state management, item quantity adjustments, total price calculation, and checkout summaries.

The Problem

Build an advanced E-commerce Shopping Cart in React demonstrating product listing grids, cart state management, item quantity adjustments, total price calculation, and checkout summaries.

Real-World Use Case

Build an advanced E-commerce Shopping Cart in React demonstrating product listing grids, cart state management, item quantity adjustments, total price calculation, and checkout summaries.

Technology Stack

Proficiency in React hooks (useState, useEffect, useMemo)

Prerequisite

Understanding of component props and callback functions

Prerequisite

Familiarity with Tailwind CSS styling

Prerequisite

Architecture & Design

Folder Structure

ecommerce_cart/
├── src/
│   ├── components/
│   │   ├── ProductCard.jsx
│   │   ├── CartDrawer.jsx
│   │   └── CartItem.jsx
│   ├── data/products.js
│   ├── App.jsx
│   └── index.css
└── package.json

Step-by-Step Implementation

1

Define sample product catalog array.

### Step 1: Project Setup Initialize Vite React project and create sample products data file.

react
import { useState, useMemo } from 'react';

const INITIAL_PRODUCTS = [
  { id: '1', name: 'Wireless Headphones', price: 99.99, image: '🎧' },
  { id: '2', name: 'Smart Watch', price: 199.99, image: '⌚' },
  { id: '3', name: 'Mechanical Keyboard', price: 149.99, image: '⌨️' },
  { id: '4', name: 'Ergonomic Mouse', price: 59.99, image: '🖱️' },
];

export default function EcommerceCart() {
  const [cart, setCart] = useState([]);
  const [isOpen, setIsOpen] = useState(false);

  const addToCart = (product) => {
    setCart(prev => {
      const existing = prev.find(item => item.id === product.id);
      if (existing) {
        return prev.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item);
      }
      return [...prev, { ...product, quantity: 1 }];
    });
    setIsOpen(true);
  };

  const updateQuantity = (id, amount) => {
    setCart(prev => prev.map(item => {
      if (item.id === id) {
        const newQty = item.quantity + amount;
        return newQty > 0 ? { ...item, quantity: newQty } : null;
      }
      return item;
    }).filter(Boolean));
  };

  const cartTotal = useMemo(() => {
    return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  }, [cart]);

  const totalItems = useMemo(() => {
    return cart.reduce((sum, item) => sum + item.quantity, 0);
  }, [cart]);

  return (
    <div className="max-w-5xl mx-auto my-10 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
      <div className="flex justify-between items-center mb-8 pb-4 border-b border-slate-100">
        <h1 className="text-2xl font-bold text-slate-900">React Storefront</h1>
        <button onClick={() => setIsOpen(!isOpen)} className="flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 font-bold rounded-xl text-xs hover:bg-blue-100 transition">
          🛒 Cart ({totalItems})
        </button>
      </div>

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
        {INITIAL_PRODUCTS.map(product => (
          <div key={product.id} className="p-5 bg-slate-50 rounded-2xl border border-slate-200 flex flex-col items-center text-center">
            <span className="text-6xl mb-4">{product.image}</span>
            <h3 className="font-bold text-slate-900 text-sm mb-1">{product.name}</h3>
            <p className="text-xs text-slate-600 mb-4">$ {product.price.toFixed(2)}</p>
            <button onClick={() => addToCart(product)} className="w-full py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl text-xs transition shadow-xs">
              Add to Cart
            </button>
          </div>
        ))}
      </div>

      {isOpen && (
        <div className="fixed inset-y-0 right-0 w-96 bg-white shadow-2xl border-l border-slate-200 p-6 flex flex-col z-50 animate-in slide-in-from-right">
          <div className="flex justify-between items-center mb-6 pb-4 border-b border-slate-100">
            <h2 className="font-bold text-lg text-slate-900">Your Shopping Cart</h2>
            <button onClick={() => setIsOpen(false)} className="text-slate-400 hover:text-slate-600 text-sm font-bold">✕</button>
          </div>

          <div className="flex-1 overflow-y-auto space-y-4">
            {cart.length === 0 ? (
              <p className="text-xs text-slate-400 text-center py-12 italic">Your cart is empty.</p>
            ) : (
              cart.map(item => (
                <div key={item.id} className="flex justify-between items-center p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
                  <div>
                    <h4 className="font-bold text-slate-900">{item.name}</h4>
                    <p className="text-slate-500">$ {(item.price * item.quantity).toFixed(2)}</p>
                  </div>
                  <div className="flex items-center gap-2 font-semibold">
                    <button onClick={() => updateQuantity(item.id, -1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">-</button>
                    <span>{item.quantity}</span>
                    <button onClick={() => updateQuantity(item.id, 1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">+</button>
                  </div>
                </div>
              ))
            )}
          </div>

          <div className="pt-4 border-t border-slate-100 mt-6">
            <div className="flex justify-between font-bold text-slate-900 text-sm mb-4">
              <span>Total:</span>
              <span>$ {cartTotal.toFixed(2)}</span>
            </div>
            <button 
              onClick={() => { alert('Proceeding to checkout!'); setCart([]); setIsOpen(false); }}
              disabled={cart.length === 0}
              className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-slate-300 text-white font-bold rounded-xl text-xs transition shadow-xs"
            >
              Checkout Now
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Code Explanation

Implementation step

2

Initialize cart state array.

### Step 2: Cart Operations & Calculation Define cart state, addition, update, and deletion handlers in App.jsx.

react
import { useState, useMemo } from 'react';

const INITIAL_PRODUCTS = [
  { id: '1', name: 'Wireless Headphones', price: 99.99, image: '🎧' },
  { id: '2', name: 'Smart Watch', price: 199.99, image: '⌚' },
  { id: '3', name: 'Mechanical Keyboard', price: 149.99, image: '⌨️' },
  { id: '4', name: 'Ergonomic Mouse', price: 59.99, image: '🖱️' },
];

export default function EcommerceCart() {
  const [cart, setCart] = useState([]);
  const [isOpen, setIsOpen] = useState(false);

  const addToCart = (product) => {
    setCart(prev => {
      const existing = prev.find(item => item.id === product.id);
      if (existing) {
        return prev.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item);
      }
      return [...prev, { ...product, quantity: 1 }];
    });
    setIsOpen(true);
  };

  const updateQuantity = (id, amount) => {
    setCart(prev => prev.map(item => {
      if (item.id === id) {
        const newQty = item.quantity + amount;
        return newQty > 0 ? { ...item, quantity: newQty } : null;
      }
      return item;
    }).filter(Boolean));
  };

  const cartTotal = useMemo(() => {
    return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  }, [cart]);

  const totalItems = useMemo(() => {
    return cart.reduce((sum, item) => sum + item.quantity, 0);
  }, [cart]);

  return (
    <div className="max-w-5xl mx-auto my-10 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
      <div className="flex justify-between items-center mb-8 pb-4 border-b border-slate-100">
        <h1 className="text-2xl font-bold text-slate-900">React Storefront</h1>
        <button onClick={() => setIsOpen(!isOpen)} className="flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 font-bold rounded-xl text-xs hover:bg-blue-100 transition">
          🛒 Cart ({totalItems})
        </button>
      </div>

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
        {INITIAL_PRODUCTS.map(product => (
          <div key={product.id} className="p-5 bg-slate-50 rounded-2xl border border-slate-200 flex flex-col items-center text-center">
            <span className="text-6xl mb-4">{product.image}</span>
            <h3 className="font-bold text-slate-900 text-sm mb-1">{product.name}</h3>
            <p className="text-xs text-slate-600 mb-4">$ {product.price.toFixed(2)}</p>
            <button onClick={() => addToCart(product)} className="w-full py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl text-xs transition shadow-xs">
              Add to Cart
            </button>
          </div>
        ))}
      </div>

      {isOpen && (
        <div className="fixed inset-y-0 right-0 w-96 bg-white shadow-2xl border-l border-slate-200 p-6 flex flex-col z-50 animate-in slide-in-from-right">
          <div className="flex justify-between items-center mb-6 pb-4 border-b border-slate-100">
            <h2 className="font-bold text-lg text-slate-900">Your Shopping Cart</h2>
            <button onClick={() => setIsOpen(false)} className="text-slate-400 hover:text-slate-600 text-sm font-bold">✕</button>
          </div>

          <div className="flex-1 overflow-y-auto space-y-4">
            {cart.length === 0 ? (
              <p className="text-xs text-slate-400 text-center py-12 italic">Your cart is empty.</p>
            ) : (
              cart.map(item => (
                <div key={item.id} className="flex justify-between items-center p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
                  <div>
                    <h4 className="font-bold text-slate-900">{item.name}</h4>
                    <p className="text-slate-500">$ {(item.price * item.quantity).toFixed(2)}</p>
                  </div>
                  <div className="flex items-center gap-2 font-semibold">
                    <button onClick={() => updateQuantity(item.id, -1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">-</button>
                    <span>{item.quantity}</span>
                    <button onClick={() => updateQuantity(item.id, 1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">+</button>
                  </div>
                </div>
              ))
            )}
          </div>

          <div className="pt-4 border-t border-slate-100 mt-6">
            <div className="flex justify-between font-bold text-slate-900 text-sm mb-4">
              <span>Total:</span>
              <span>$ {cartTotal.toFixed(2)}</span>
            </div>
            <button 
              onClick={() => { alert('Proceeding to checkout!'); setCart([]); setIsOpen(false); }}
              disabled={cart.length === 0}
              className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-slate-300 text-white font-bold rounded-xl text-xs transition shadow-xs"
            >
              Checkout Now
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Code Explanation

Implementation step

3

Implement addToCart function that checks if item already exists; if so, increments quantity.

### Step 3: Components & UI Layout Create ProductCard grid and CartDrawer modal.

react
import { useState, useMemo } from 'react';

const INITIAL_PRODUCTS = [
  { id: '1', name: 'Wireless Headphones', price: 99.99, image: '🎧' },
  { id: '2', name: 'Smart Watch', price: 199.99, image: '⌚' },
  { id: '3', name: 'Mechanical Keyboard', price: 149.99, image: '⌨️' },
  { id: '4', name: 'Ergonomic Mouse', price: 59.99, image: '🖱️' },
];

export default function EcommerceCart() {
  const [cart, setCart] = useState([]);
  const [isOpen, setIsOpen] = useState(false);

  const addToCart = (product) => {
    setCart(prev => {
      const existing = prev.find(item => item.id === product.id);
      if (existing) {
        return prev.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item);
      }
      return [...prev, { ...product, quantity: 1 }];
    });
    setIsOpen(true);
  };

  const updateQuantity = (id, amount) => {
    setCart(prev => prev.map(item => {
      if (item.id === id) {
        const newQty = item.quantity + amount;
        return newQty > 0 ? { ...item, quantity: newQty } : null;
      }
      return item;
    }).filter(Boolean));
  };

  const cartTotal = useMemo(() => {
    return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  }, [cart]);

  const totalItems = useMemo(() => {
    return cart.reduce((sum, item) => sum + item.quantity, 0);
  }, [cart]);

  return (
    <div className="max-w-5xl mx-auto my-10 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
      <div className="flex justify-between items-center mb-8 pb-4 border-b border-slate-100">
        <h1 className="text-2xl font-bold text-slate-900">React Storefront</h1>
        <button onClick={() => setIsOpen(!isOpen)} className="flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 font-bold rounded-xl text-xs hover:bg-blue-100 transition">
          🛒 Cart ({totalItems})
        </button>
      </div>

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
        {INITIAL_PRODUCTS.map(product => (
          <div key={product.id} className="p-5 bg-slate-50 rounded-2xl border border-slate-200 flex flex-col items-center text-center">
            <span className="text-6xl mb-4">{product.image}</span>
            <h3 className="font-bold text-slate-900 text-sm mb-1">{product.name}</h3>
            <p className="text-xs text-slate-600 mb-4">$ {product.price.toFixed(2)}</p>
            <button onClick={() => addToCart(product)} className="w-full py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl text-xs transition shadow-xs">
              Add to Cart
            </button>
          </div>
        ))}
      </div>

      {isOpen && (
        <div className="fixed inset-y-0 right-0 w-96 bg-white shadow-2xl border-l border-slate-200 p-6 flex flex-col z-50 animate-in slide-in-from-right">
          <div className="flex justify-between items-center mb-6 pb-4 border-b border-slate-100">
            <h2 className="font-bold text-lg text-slate-900">Your Shopping Cart</h2>
            <button onClick={() => setIsOpen(false)} className="text-slate-400 hover:text-slate-600 text-sm font-bold">✕</button>
          </div>

          <div className="flex-1 overflow-y-auto space-y-4">
            {cart.length === 0 ? (
              <p className="text-xs text-slate-400 text-center py-12 italic">Your cart is empty.</p>
            ) : (
              cart.map(item => (
                <div key={item.id} className="flex justify-between items-center p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
                  <div>
                    <h4 className="font-bold text-slate-900">{item.name}</h4>
                    <p className="text-slate-500">$ {(item.price * item.quantity).toFixed(2)}</p>
                  </div>
                  <div className="flex items-center gap-2 font-semibold">
                    <button onClick={() => updateQuantity(item.id, -1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">-</button>
                    <span>{item.quantity}</span>
                    <button onClick={() => updateQuantity(item.id, 1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">+</button>
                  </div>
                </div>
              ))
            )}
          </div>

          <div className="pt-4 border-t border-slate-100 mt-6">
            <div className="flex justify-between font-bold text-slate-900 text-sm mb-4">
              <span>Total:</span>
              <span>$ {cartTotal.toFixed(2)}</span>
            </div>
            <button 
              onClick={() => { alert('Proceeding to checkout!'); setCart([]); setIsOpen(false); }}
              disabled={cart.length === 0}
              className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-slate-300 text-white font-bold rounded-xl text-xs transition shadow-xs"
            >
              Checkout Now
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Code Explanation

Implementation step

4

Implement updateQuantity function adjusting quantity or removing item if quantity drops to 0.

### Step 4: Edge Cases Handle out-of-stock items and checkout success flows gracefully.

react
import { useState, useMemo } from 'react';

const INITIAL_PRODUCTS = [
  { id: '1', name: 'Wireless Headphones', price: 99.99, image: '🎧' },
  { id: '2', name: 'Smart Watch', price: 199.99, image: '⌚' },
  { id: '3', name: 'Mechanical Keyboard', price: 149.99, image: '⌨️' },
  { id: '4', name: 'Ergonomic Mouse', price: 59.99, image: '🖱️' },
];

export default function EcommerceCart() {
  const [cart, setCart] = useState([]);
  const [isOpen, setIsOpen] = useState(false);

  const addToCart = (product) => {
    setCart(prev => {
      const existing = prev.find(item => item.id === product.id);
      if (existing) {
        return prev.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item);
      }
      return [...prev, { ...product, quantity: 1 }];
    });
    setIsOpen(true);
  };

  const updateQuantity = (id, amount) => {
    setCart(prev => prev.map(item => {
      if (item.id === id) {
        const newQty = item.quantity + amount;
        return newQty > 0 ? { ...item, quantity: newQty } : null;
      }
      return item;
    }).filter(Boolean));
  };

  const cartTotal = useMemo(() => {
    return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  }, [cart]);

  const totalItems = useMemo(() => {
    return cart.reduce((sum, item) => sum + item.quantity, 0);
  }, [cart]);

  return (
    <div className="max-w-5xl mx-auto my-10 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
      <div className="flex justify-between items-center mb-8 pb-4 border-b border-slate-100">
        <h1 className="text-2xl font-bold text-slate-900">React Storefront</h1>
        <button onClick={() => setIsOpen(!isOpen)} className="flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 font-bold rounded-xl text-xs hover:bg-blue-100 transition">
          🛒 Cart ({totalItems})
        </button>
      </div>

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
        {INITIAL_PRODUCTS.map(product => (
          <div key={product.id} className="p-5 bg-slate-50 rounded-2xl border border-slate-200 flex flex-col items-center text-center">
            <span className="text-6xl mb-4">{product.image}</span>
            <h3 className="font-bold text-slate-900 text-sm mb-1">{product.name}</h3>
            <p className="text-xs text-slate-600 mb-4">$ {product.price.toFixed(2)}</p>
            <button onClick={() => addToCart(product)} className="w-full py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl text-xs transition shadow-xs">
              Add to Cart
            </button>
          </div>
        ))}
      </div>

      {isOpen && (
        <div className="fixed inset-y-0 right-0 w-96 bg-white shadow-2xl border-l border-slate-200 p-6 flex flex-col z-50 animate-in slide-in-from-right">
          <div className="flex justify-between items-center mb-6 pb-4 border-b border-slate-100">
            <h2 className="font-bold text-lg text-slate-900">Your Shopping Cart</h2>
            <button onClick={() => setIsOpen(false)} className="text-slate-400 hover:text-slate-600 text-sm font-bold">✕</button>
          </div>

          <div className="flex-1 overflow-y-auto space-y-4">
            {cart.length === 0 ? (
              <p className="text-xs text-slate-400 text-center py-12 italic">Your cart is empty.</p>
            ) : (
              cart.map(item => (
                <div key={item.id} className="flex justify-between items-center p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
                  <div>
                    <h4 className="font-bold text-slate-900">{item.name}</h4>
                    <p className="text-slate-500">$ {(item.price * item.quantity).toFixed(2)}</p>
                  </div>
                  <div className="flex items-center gap-2 font-semibold">
                    <button onClick={() => updateQuantity(item.id, -1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">-</button>
                    <span>{item.quantity}</span>
                    <button onClick={() => updateQuantity(item.id, 1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">+</button>
                  </div>
                </div>
              ))
            )}
          </div>

          <div className="pt-4 border-t border-slate-100 mt-6">
            <div className="flex justify-between font-bold text-slate-900 text-sm mb-4">
              <span>Total:</span>
              <span>$ {cartTotal.toFixed(2)}</span>
            </div>
            <button 
              onClick={() => { alert('Proceeding to checkout!'); setCart([]); setIsOpen(false); }}
              disabled={cart.length === 0}
              className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-slate-300 text-white font-bold rounded-xl text-xs transition shadow-xs"
            >
              Checkout Now
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Code Explanation

Implementation step

5

Calculate total items and total price using useMemo hook.

### Step 4: Edge Cases Handle out-of-stock items and checkout success flows gracefully.

react
import { useState, useMemo } from 'react';

const INITIAL_PRODUCTS = [
  { id: '1', name: 'Wireless Headphones', price: 99.99, image: '🎧' },
  { id: '2', name: 'Smart Watch', price: 199.99, image: '⌚' },
  { id: '3', name: 'Mechanical Keyboard', price: 149.99, image: '⌨️' },
  { id: '4', name: 'Ergonomic Mouse', price: 59.99, image: '🖱️' },
];

export default function EcommerceCart() {
  const [cart, setCart] = useState([]);
  const [isOpen, setIsOpen] = useState(false);

  const addToCart = (product) => {
    setCart(prev => {
      const existing = prev.find(item => item.id === product.id);
      if (existing) {
        return prev.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item);
      }
      return [...prev, { ...product, quantity: 1 }];
    });
    setIsOpen(true);
  };

  const updateQuantity = (id, amount) => {
    setCart(prev => prev.map(item => {
      if (item.id === id) {
        const newQty = item.quantity + amount;
        return newQty > 0 ? { ...item, quantity: newQty } : null;
      }
      return item;
    }).filter(Boolean));
  };

  const cartTotal = useMemo(() => {
    return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  }, [cart]);

  const totalItems = useMemo(() => {
    return cart.reduce((sum, item) => sum + item.quantity, 0);
  }, [cart]);

  return (
    <div className="max-w-5xl mx-auto my-10 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
      <div className="flex justify-between items-center mb-8 pb-4 border-b border-slate-100">
        <h1 className="text-2xl font-bold text-slate-900">React Storefront</h1>
        <button onClick={() => setIsOpen(!isOpen)} className="flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 font-bold rounded-xl text-xs hover:bg-blue-100 transition">
          🛒 Cart ({totalItems})
        </button>
      </div>

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
        {INITIAL_PRODUCTS.map(product => (
          <div key={product.id} className="p-5 bg-slate-50 rounded-2xl border border-slate-200 flex flex-col items-center text-center">
            <span className="text-6xl mb-4">{product.image}</span>
            <h3 className="font-bold text-slate-900 text-sm mb-1">{product.name}</h3>
            <p className="text-xs text-slate-600 mb-4">$ {product.price.toFixed(2)}</p>
            <button onClick={() => addToCart(product)} className="w-full py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl text-xs transition shadow-xs">
              Add to Cart
            </button>
          </div>
        ))}
      </div>

      {isOpen && (
        <div className="fixed inset-y-0 right-0 w-96 bg-white shadow-2xl border-l border-slate-200 p-6 flex flex-col z-50 animate-in slide-in-from-right">
          <div className="flex justify-between items-center mb-6 pb-4 border-b border-slate-100">
            <h2 className="font-bold text-lg text-slate-900">Your Shopping Cart</h2>
            <button onClick={() => setIsOpen(false)} className="text-slate-400 hover:text-slate-600 text-sm font-bold">✕</button>
          </div>

          <div className="flex-1 overflow-y-auto space-y-4">
            {cart.length === 0 ? (
              <p className="text-xs text-slate-400 text-center py-12 italic">Your cart is empty.</p>
            ) : (
              cart.map(item => (
                <div key={item.id} className="flex justify-between items-center p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
                  <div>
                    <h4 className="font-bold text-slate-900">{item.name}</h4>
                    <p className="text-slate-500">$ {(item.price * item.quantity).toFixed(2)}</p>
                  </div>
                  <div className="flex items-center gap-2 font-semibold">
                    <button onClick={() => updateQuantity(item.id, -1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">-</button>
                    <span>{item.quantity}</span>
                    <button onClick={() => updateQuantity(item.id, 1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">+</button>
                  </div>
                </div>
              ))
            )}
          </div>

          <div className="pt-4 border-t border-slate-100 mt-6">
            <div className="flex justify-between font-bold text-slate-900 text-sm mb-4">
              <span>Total:</span>
              <span>$ {cartTotal.toFixed(2)}</span>
            </div>
            <button 
              onClick={() => { alert('Proceeding to checkout!'); setCart([]); setIsOpen(false); }}
              disabled={cart.length === 0}
              className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-slate-300 text-white font-bold rounded-xl text-xs transition shadow-xs"
            >
              Checkout Now
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Code Explanation

Implementation step

6

Render product catalog and sliding cart drawer UI.

### Step 4: Edge Cases Handle out-of-stock items and checkout success flows gracefully.

react
import { useState, useMemo } from 'react';

const INITIAL_PRODUCTS = [
  { id: '1', name: 'Wireless Headphones', price: 99.99, image: '🎧' },
  { id: '2', name: 'Smart Watch', price: 199.99, image: '⌚' },
  { id: '3', name: 'Mechanical Keyboard', price: 149.99, image: '⌨️' },
  { id: '4', name: 'Ergonomic Mouse', price: 59.99, image: '🖱️' },
];

export default function EcommerceCart() {
  const [cart, setCart] = useState([]);
  const [isOpen, setIsOpen] = useState(false);

  const addToCart = (product) => {
    setCart(prev => {
      const existing = prev.find(item => item.id === product.id);
      if (existing) {
        return prev.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item);
      }
      return [...prev, { ...product, quantity: 1 }];
    });
    setIsOpen(true);
  };

  const updateQuantity = (id, amount) => {
    setCart(prev => prev.map(item => {
      if (item.id === id) {
        const newQty = item.quantity + amount;
        return newQty > 0 ? { ...item, quantity: newQty } : null;
      }
      return item;
    }).filter(Boolean));
  };

  const cartTotal = useMemo(() => {
    return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  }, [cart]);

  const totalItems = useMemo(() => {
    return cart.reduce((sum, item) => sum + item.quantity, 0);
  }, [cart]);

  return (
    <div className="max-w-5xl mx-auto my-10 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
      <div className="flex justify-between items-center mb-8 pb-4 border-b border-slate-100">
        <h1 className="text-2xl font-bold text-slate-900">React Storefront</h1>
        <button onClick={() => setIsOpen(!isOpen)} className="flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 font-bold rounded-xl text-xs hover:bg-blue-100 transition">
          🛒 Cart ({totalItems})
        </button>
      </div>

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
        {INITIAL_PRODUCTS.map(product => (
          <div key={product.id} className="p-5 bg-slate-50 rounded-2xl border border-slate-200 flex flex-col items-center text-center">
            <span className="text-6xl mb-4">{product.image}</span>
            <h3 className="font-bold text-slate-900 text-sm mb-1">{product.name}</h3>
            <p className="text-xs text-slate-600 mb-4">$ {product.price.toFixed(2)}</p>
            <button onClick={() => addToCart(product)} className="w-full py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl text-xs transition shadow-xs">
              Add to Cart
            </button>
          </div>
        ))}
      </div>

      {isOpen && (
        <div className="fixed inset-y-0 right-0 w-96 bg-white shadow-2xl border-l border-slate-200 p-6 flex flex-col z-50 animate-in slide-in-from-right">
          <div className="flex justify-between items-center mb-6 pb-4 border-b border-slate-100">
            <h2 className="font-bold text-lg text-slate-900">Your Shopping Cart</h2>
            <button onClick={() => setIsOpen(false)} className="text-slate-400 hover:text-slate-600 text-sm font-bold">✕</button>
          </div>

          <div className="flex-1 overflow-y-auto space-y-4">
            {cart.length === 0 ? (
              <p className="text-xs text-slate-400 text-center py-12 italic">Your cart is empty.</p>
            ) : (
              cart.map(item => (
                <div key={item.id} className="flex justify-between items-center p-3 bg-slate-50 rounded-xl border border-slate-100 text-xs">
                  <div>
                    <h4 className="font-bold text-slate-900">{item.name}</h4>
                    <p className="text-slate-500">$ {(item.price * item.quantity).toFixed(2)}</p>
                  </div>
                  <div className="flex items-center gap-2 font-semibold">
                    <button onClick={() => updateQuantity(item.id, -1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">-</button>
                    <span>{item.quantity}</span>
                    <button onClick={() => updateQuantity(item.id, 1)} className="w-6 h-6 bg-slate-200 rounded hover:bg-slate-300">+</button>
                  </div>
                </div>
              ))
            )}
          </div>

          <div className="pt-4 border-t border-slate-100 mt-6">
            <div className="flex justify-between font-bold text-slate-900 text-sm mb-4">
              <span>Total:</span>
              <span>$ {cartTotal.toFixed(2)}</span>
            </div>
            <button 
              onClick={() => { alert('Proceeding to checkout!'); setCart([]); setIsOpen(false); }}
              disabled={cart.length === 0}
              className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-slate-300 text-white font-bold rounded-xl text-xs transition shadow-xs"
            >
              Checkout Now
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Code Explanation

Implementation step

Common Errors

Cart drawer doesn't close.

Attach setIsOpen(false) to close button.

Security & Performance

Click Add to Cart on multiple products and verify cart drawer opens.

Confirm item quantities increment correctly without adding duplicate rows.

Check total price calculations.

Click Checkout button and verify cart clears.


Add coupon code reduction logic.

Add product search filtering.

Integrate Stripe checkout mock.

Interview Questions

Q: Why use useMemo for cartTotal?

A: It caches the total calculation and only recalculates when the cart array changes, optimizing performance.

Growth Newsletter

Get practical AI tools, SEO tips, and growth guides weekly.

Join creators, students, and businesses scaling with TechIdea.