Shopping Cart — Objects, Reduce, Event Delegation & Templates
Cart ka total ek line mein nikalo (Reduce), buttons ko ek jagah sambhalo (Delegation), aur kabhi bhi catalog data mutate mat karo!
The cart is an array of objects: each item holds id, name, price, and quantity. The product catalog is a separate data source — the cart only stores references (the id) and how many the user wants.
The key rule: if an item already exists in the cart, increment its quantity instead of adding a duplicate entry. Use findIndex or find to check for existing items.
let cart = [];
function addToCart(product) {
const existingIndex = cart.findIndex(item => item.id === product.id);
if (existingIndex !== -1) {
cart[existingIndex].quantity += 1;
} else {
cart.push({ ...product, quantity: 1 });
}
renderCart();
}
function removeFromCart(productId) {
cart = cart.filter(item => item.id !== productId);
renderCart();
}
function updateQuantity(productId, newQty) {
if (newQty <= 0) {
removeFromCart(productId);
return;
}
const item = cart.find(item => item.id === productId);
if (item) item.quantity = newQty;
renderCart();
}
Array.splice() mutates the original array and requires index calculation. filter returns a brand new array — cleaner for state management. If you ever switch to a reactive framework (React, Angular), immutable updates (filter, map, spread) are the standard pattern.Three core operations: addToCart (add or increment), removeFromCart (filter out), and updateQuantity (set new qty or remove if zero). Every cart action boils down to one of these three.
reduce is perfect for calculating totals from an array of objects. Instead of writing a for loop with a mutable variable, reduce takes the array and folds it into a single value — the total price.
Multiply price * quantity for each item, then accumulate the sum. You can also count total items the same way.
const cart = [
{ id: 1, name: "Laptop", price: 999, quantity: 1 },
{ id: 2, name: "Mouse", price: 25, quantity: 2 },
{ id: 3, name: "Keyboard", price: 50, quantity: 1 }
];
// Total Price
const totalPrice = cart.reduce((total, item) => {
return total + (item.price * item.quantity);
}, 0); // 999 + 50 + 50 = 1099
// Total Items Count
const totalItems = cart.reduce((count, item) => {
return count + item.quantity;
}, 0); // 1 + 2 + 1 = 4
// Format to currency
const formatted = new Intl.NumberFormat("en-IN", {
style: "currency", currency: "INR"
}).format(totalPrice);
0 and the cart is empty, reduce throws TypeError: Reduce of empty array with no initial value. Always provide 0 for sum operations — it also makes the intent clear: "start from zero and accumulate."Why reduce over for: No mutable external variable, no off-by-one index bugs, and the intent is declarative — "reduce this array to a single sum." This is the industry standard for deriving totals from arrays.
A cart has multiple buttons per item: +, -, and Delete. Adding a separate event listener to each button is inefficient and creates memory leaks when the DOM re-renders.
Event Delegation: Attach ONE listener to the cart container. Use data-action and data-id attributes on buttons. The handler reads the action, finds the product ID, and calls the corresponding state function.
const cartContainer = document.getElementById("cart-items");
cartContainer.addEventListener("click", (e) => {
const btn = e.target.closest("button");
if (!btn) return;
const action = btn.dataset.action;
const id = parseInt(btn.dataset.id);
switch (action) {
case "increase":
const item = cart.find(i => i.id === id);
if (item) updateQuantity(id, item.quantity + 1);
break;
case "decrease":
const itemD = cart.find(i => i.id === id);
if (itemD) updateQuantity(id, itemD.quantity - 1);
break;
case "remove":
removeFromCart(id);
break;
}
});
// Button Template
function itemTemplate(item) {
return '' +
'' + item.quantity + '' +
'' +
'';
}
Why closest('button'): Sometimes the click lands on an icon or span inside the button. closest walks up the DOM tree to find the actual button element that holds the data-* attributes. This prevents undefined errors and makes the handler robust.
Template rendering means building the HTML string from your data and updating the DOM in one shot. Use map().join('') to convert the cart array into an HTML string.
Template functions that return strings keep your rendering logic clean and separate from your event logic. Update the DOM once by setting innerHTML, then update the total price span separately.
function renderCart() {
const cartItemsDiv = document.getElementById("cart-items");
const cartTotalSpan = document.getElementById("cart-total");
if (cart.length === 0) {
cartItemsDiv.innerHTML = 'Your cart is empty.
';
cartTotalSpan.textContent = '₹0';
return;
}
// Build HTML string
const html = cart.map(item => {
return '' +
'' + item.name + ' (₹' + item.price + ')
' +
'' + itemTemplate(item) + '' +
'Subtotal: ₹' + (item.price * item.quantity) + '
' +
'';
}).join('');
// Single DOM update
cartItemsDiv.innerHTML = html;
// Update totals
const total = cart.reduce((sum, i) => sum + (i.price * i.quantity), 0);
cartTotalSpan.textContent = '₹' + total;
}
Before checking out, validate user details like Name, Address, and Phone. Use the Constraint Validation API or custom regex. Disable the checkout button until the form is valid, and give real-time feedback as the user types.
const checkoutForm = document.getElementById("checkout-form");
checkoutForm.addEventListener("submit", (e) => {
e.preventDefault();
const name = document.getElementById("name").value.trim();
const phone = document.getElementById("phone").value.trim();
// Validation
if (name.length < 3) {
showError("Name must be at least 3 characters");
return;
}
const phoneRegex = /^[6-9]\d{9}$/; // Indian mobile
if (!phoneRegex.test(phone)) {
showError("Enter a valid 10-digit phone number");
return;
}
// If valid, proceed with order
processOrder(name, phone, cart);
});
function processOrder(name, phone, items) {
const total = items.reduce((sum, i) => sum + (i.price * i.quantity), 0);
console.log("Order placed!", { name, phone, total });
cart = [];
renderCart();
}
Lo kar liya — Key Points:
- ✅ Design the cart state as an array of objects holding
id,price, andquantity, checking for duplicates before adding - ✅ Use
reduceto elegantly calculate the total price and total item count from the cart array - ✅ Implement Event Delegation on the cart container using
data-actionanddata-idattributes to handle increment, decrement, and delete actions - ✅ Build HTML strings using
map().join('')and update the DOM once viainnerHTMLto batch reflows - ✅ Validate checkout forms using regex and constraint checks, providing real-time feedback before processing the order
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login