In the dynamic world of web development, creating e-commerce applications has become increasingly accessible. The core of any online store is the shopping cart, the digital space where customers gather their desired products before heading to checkout. This article guides you through building a simple, yet functional, interactive shopping cart app using Next.js, a powerful React framework. We’ll explore the essential components, step-by-step implementation, and common pitfalls to ensure a smooth learning experience for beginners to intermediate developers. This project aims to solidify your understanding of state management, component interaction, and data handling within a modern web application.
Why Build a Shopping Cart?
Building a shopping cart app is an excellent project for several reasons:
- Practical Application: It directly translates to real-world e-commerce scenarios, allowing you to apply your skills in a practical context.
- Core Concepts: It involves key concepts like state management, component re-rendering, and user interaction, fundamental to front-end development.
- Scalability: The basic structure can be easily extended to incorporate more complex features like product variations, promotions, and payment gateway integration.
- Learning Curve: It’s complex enough to challenge you, yet manageable enough to complete within a reasonable timeframe.
Prerequisites
Before we dive in, ensure you have the following:
- Node.js and npm (or yarn): Required for package management and running the Next.js development server.
- A Code Editor: VS Code, Sublime Text, or any editor of your choice.
- Basic HTML, CSS, and JavaScript knowledge: Familiarity with these technologies is essential for understanding the code.
- A Terminal/Command Line: For running commands and managing your project.
Setting Up Your Next.js Project
Let’s start by creating a new Next.js project. Open your terminal and run the following command:
npx create-next-app shopping-cart-app
This command sets up a basic Next.js project with all the necessary dependencies. Navigate into your project directory:
cd shopping-cart-app
Now, start the development server:
npm run dev
This will typically start your application on http://localhost:3000. Open this in your browser to see the default Next.js welcome page. Now, let’s structure our project.
Project Structure
A well-organized project structure is crucial for maintainability. Here’s a suggested structure:
shopping-cart-app/
├── components/
│ ├── ProductCard.js
│ ├── CartItem.js
│ └── CartSummary.js
├── pages/
│ ├── index.js
│ └── cart.js
├── styles/
│ └── globals.css
└── package.json
Let’s create these files and start implementing our components. We will use the components folder for reusable UI elements and the pages folder for our different routes (home page and cart page).
Creating the ProductCard Component
The ProductCard component will display individual product information. Create a file named ProductCard.js inside the components directory. Here’s a basic implementation:
import styles from '../styles/globals.css';
function ProductCard({ product, addToCart }) {
return (
<div className={styles.productCard}>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => addToCart(product)}>Add to Cart</button>
</div>
);
}
export default ProductCard;
This component receives a product object (which we will define later) and an addToCart function as props. It displays the product image, name, price, and an “Add to Cart” button. The addToCart function will be responsible for adding the product to the cart.
Creating the CartItem Component
The CartItem component will display individual items within the shopping cart. Create CartItem.js inside the components directory:
import styles from '../styles/globals.css';
function CartItem({ item, removeItem, updateQuantity }) {
return (
<div className={styles.cartItem}>
<img src={item.product.image} alt={item.product.name} />
<p>{item.product.name}</p>
<p>${item.product.price}</p>
<input
type="number"
min="1"
value={item.quantity}
onChange={(e) => updateQuantity(item.product.id, parseInt(e.target.value))}
/>
<button onClick={() => removeItem(item.product.id)}>Remove</button>
</div>
);
}
export default CartItem;
This component receives an item object (containing the product details and quantity), a removeItem function, and an updateQuantity function as props. It displays the item’s image, name, price, a quantity input, and a remove button.
Creating the CartSummary Component
The CartSummary component will display the total cost of items in the cart. Create CartSummary.js inside the components directory:
import styles from '../styles/globals.css';
function CartSummary({ cartItems }) {
const total = cartItems.reduce((sum, item) => sum + item.product.price * item.quantity, 0);
return (
<div className={styles.cartSummary}>
<h3>Cart Summary</h3>
<p>Total: ${total.toFixed(2)}</p>
</div>
);
}
export default CartSummary;
This component receives the cartItems array as a prop and calculates the total cost. It then displays the total.
Building the Home Page (index.js)
The home page will display a list of products. Open pages/index.js and replace the default content with the following:
import { useState } from 'react';
import ProductCard from '../components/ProductCard';
import styles from '../styles/globals.css';
const products = [
{ id: 1, name: 'Product 1', price: 19.99, image: '/product1.jpg' },
{ id: 2, name: 'Product 2', price: 29.99, image: '/product2.jpg' },
{ id: 3, name: 'Product 3', price: 9.99, image: '/product3.jpg' },
];
export default function Home() {
const [cart, setCart] = useState([]);
const addToCart = (product) => {
const existingItemIndex = cart.findIndex((item) => item.product.id === product.id);
if (existingItemIndex !== -1) {
const updatedCart = [...cart];
updatedCart[existingItemIndex].quantity += 1;
setCart(updatedCart);
} else {
setCart([...cart, { product, quantity: 1 }]);
}
};
return (
<div className={styles.container}>
<header className={styles.header}>
<h1>Shopping Cart</h1>
<a href="/cart" className={styles.cartLink}>View Cart ({cart.reduce((sum, item) => sum + item.quantity, 0)})</a>
</header>
<main className={styles.main}>
<div className={styles.productGrid}>
{products.map((product) => (
<ProductCard key={product.id} product={product} addToCart={addToCart} />
))}
</div>
</main>
</div>
);
}
Here’s what this code does:
- Imports: Imports necessary components and the
useStatehook. - Product Data: Defines a sample array of
products. In a real application, this data would likely come from an API or database. - State Management: Uses
useStateto manage thecartstate, which is an array of objects. Each object represents an item in the cart and contains the product details and the quantity. addToCartFunction: This function is called when the “Add to Cart” button is clicked. It checks if the product already exists in the cart. If it does, it increments the quantity. If not, it adds the product to the cart with a quantity of 1.- Rendering Products: Maps over the
productsarray and renders aProductCardcomponent for each product, passing the product data and theaddToCartfunction as props. - Navigation: Includes a link to the cart page, displaying the number of items in the cart.
Building the Cart Page (cart.js)
The cart page will display the items in the cart and allow users to modify quantities and remove items. Open pages/cart.js and add the following code:
import { useState, useEffect } from 'react';
import CartItem from '../components/CartItem';
import CartSummary from '../components/CartSummary';
import styles from '../styles/globals.css';
export default function Cart() {
const [cartItems, setCartItems] = useState([]);
useEffect(() => {
if (typeof window !== 'undefined') {
const cartData = localStorage.getItem('cart') ? JSON.parse(localStorage.getItem('cart')) : [];
setCartItems(cartData);
}
}, []);
useEffect(() => {
if (typeof window !== 'undefined') {
localStorage.setItem('cart', JSON.stringify(cartItems));
}
}, [cartItems]);
const removeItem = (productId) => {
const updatedCart = cartItems.filter((item) => item.product.id !== productId);
setCartItems(updatedCart);
};
const updateQuantity = (productId, quantity) => {
const updatedCart = cartItems.map((item) => {
if (item.product.id === productId) {
return { ...item, quantity: quantity };
}
return item;
});
setCartItems(updatedCart);
};
return (
<div className={styles.container}>
<header className={styles.header}>
<h1>Your Cart</h1>
<a href="/" className={styles.cartLink}>Back to Products</a>
</header>
<main className={styles.main}>
{cartItems.length === 0 ? (
<p>Your cart is empty.</p>
) : (
<div>
{cartItems.map((item) => (
<CartItem
key={item.product.id}
item={item}
removeItem={removeItem}
updateQuantity={updateQuantity}
/>
))}
<CartSummary cartItems={cartItems} />
</div>
)}
</main>
</div>
);
}
Here’s what this code does:
- Imports: Imports necessary components and the
useStateanduseEffecthooks. - State Management: Uses
useStateto manage thecartItemsstate, which holds the items in the cart. - Local Storage: Uses
useEffecthooks to load and save the cart data to local storage. This ensures the cart persists across page reloads. The firstuseEffectretrieves cart data from local storage when the component mounts. The seconduseEffectsaves cart data to local storage whenevercartItemschanges. removeItemFunction: This function is called when the “Remove” button is clicked. It filters thecartItemsarray, removing the item with the specifiedproductId.updateQuantityFunction: This function is called when the quantity input changes. It updates the quantity of the specified item in thecartItemsarray.- Conditional Rendering: Displays a message if the cart is empty or renders the
CartItemcomponents for each item in the cart. - Cart Summary: Renders the
CartSummarycomponent, passing thecartItemsas a prop.
Styling with CSS Modules
For this project, we’ll use CSS Modules for styling. CSS Modules provide a way to scope your CSS styles to specific components, preventing style conflicts. Next.js has built-in support for CSS Modules. Open styles/globals.css and add the following styles. Note that the class names in the CSS must match the class names used in your components. You can customize these styles to match your design preferences.
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.header {
width: 100%;
padding: 1rem 0;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eaeaea;
}
.main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.productCard {
border: 1px solid #ccc;
padding: 1rem;
margin: 1rem;
width: 200px;
text-align: center;
}
.productCard img {
max-width: 100%;
height: 150px;
object-fit: contain;
margin-bottom: 0.5rem;
}
.cartLink {
color: blue;
text-decoration: none;
}
.cartItem {
display: flex;
align-items: center;
margin: 1rem 0;
border: 1px solid #eee;
padding: 0.5rem;
width: 500px;
justify-content: space-between;
}
.cartItem img {
width: 100px;
height: 100px;
object-fit: contain;
margin-right: 1rem;
}
.cartSummary {
margin-top: 2rem;
text-align: right;
width: 500px;
}
.productGrid {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
Ensure that the class names in the CSS match the className values used in your React components (e.g., styles.productCard). This is how CSS Modules provides scoping.
Adding Product Images
To display product images, you’ll need to add image files to your project. Create an public directory in the root of your project (if it doesn’t already exist). Place your product images (e.g., product1.jpg, product2.jpg, product3.jpg) inside the public directory. Next.js serves static assets from the public directory. The image paths in your products array in index.js should then correctly point to these images (e.g., /product1.jpg).
Testing Your Application
With all the components and styling in place, it’s time to test your application. Open your browser and navigate to http://localhost:3000. You should see the home page with a list of products. Click the “Add to Cart” button for a product. The number next to “View Cart” in the header should update to reflect the number of items in the cart. Click “View Cart” to go to the cart page. You should see the added product in the cart. You can also modify the quantity and remove items from the cart. Refresh the page to ensure the cart data persists in local storage.
Common Mistakes and Troubleshooting
Here are some common mistakes and how to fix them:
- Incorrect File Paths: Double-check the file paths for your components and images. Typographical errors are a frequent source of problems.
- Missing Imports: Ensure all necessary components and hooks (like
useStateanduseEffect) are imported correctly. - CSS Module Errors: Make sure you are using the correct class names defined in your CSS Modules file (
globals.cssin this example). Also, ensure you have imported the CSS module correctly in your component files (import styles from '../styles/globals.css';). - State Updates: When updating state (especially arrays or objects), make sure you are creating new instances of the state. Avoid directly mutating the state. For example, use the spread operator (
...) to create new arrays/objects when updating. - Local Storage Issues: If the cart data isn’t persisting, double-check that you are correctly using
localStorage.setItem()andlocalStorage.getItem(). Also, ensure that you are parsing and stringifying the data correctly withJSON.parse()andJSON.stringify(). Remember that local storage is specific to the domain, so the cart data will not persist if you change the domain. - Typos: Carefully review your code for any typos, especially in variable names, function names, and component names.
Key Takeaways
This tutorial has provided a practical guide to building a basic shopping cart application using Next.js. You’ve learned how to structure your project, manage state, create reusable components, and handle user interactions. This simple project demonstrates how to create a foundation for more complex e-commerce features. Remember to always create new instances when updating states. By understanding these core concepts, you can build upon this foundation and create more sophisticated and feature-rich applications. From here, you can extend this project by adding features such as product variations, user authentication, payment gateway integration, and more. The possibilities are vast, and the knowledge gained from this project will serve as a strong base for your web development journey.
