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.jsonStep-by-Step Implementation
Define sample product catalog array.
### Step 1: Project Setup Initialize Vite React project and create sample products data file.
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
Initialize cart state array.
### Step 2: Cart Operations & Calculation Define cart state, addition, update, and deletion handlers in App.jsx.
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
Implement addToCart function that checks if item already exists; if so, increments quantity.
### Step 3: Components & UI Layout Create ProductCard grid and CartDrawer modal.
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
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.
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
Calculate total items and total price using useMemo hook.
### Step 4: Edge Cases Handle out-of-stock items and checkout success flows gracefully.
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
Render product catalog and sliding cart drawer UI.
### Step 4: Edge Cases Handle out-of-stock items and checkout success flows gracefully.
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
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.