A-Maison Tech - Electronics E-commerce
A-Maison Tech is an electronics e-commerce platform built with the MERN stack (MongoDB, Express, React, Node.js). The authentication system is managed via JWT and bcrypt, with distinct roles for users and administrators. Administrators can manage the product catalog, view and update orders, while users can add items to the cart, complete purchases via Stripe, and view special offers. The app includes features like discount management, featured products, and daily promotions.
Dynamic Cart Item Count Synchronization Across Multiple App Views
The challenge was to create a product model that defines the structure of products available in the store, allowing both users and administrators to interact with them seamlessly. This model is crucial for the application, as it determines how products are displayed to customers, while also providing an intuitive management system for administrators to easily organize, update, and categorize products based on various attributes.
To achieve this, I created a MongoDB model using Mongoose to define the schema for the products. This schema includes fields such as title, description, price, category, images, and several flags like discounts, featured status, and new arrivals. These fields allow for flexible product management, ensuring both users and administrators have a seamless experience. I also implemented different controllers to manage the product model, each responsible for handling specific operations. For example, I created a controller to fetch all discounted products by querying the isDiscounted field. This controller makes use of the middleware set on the schema, which automatically sets the isDiscounted property to true when the discount value is greater than 0.
Step 1 -Product Model Schema Breakdown
The product schema defines various fields that are necessary for managing products within the e-commerce platform. Each field has a specific role to ensure flexibility, easy categorization, and effective handling of product data.
Here is a detailed explanation of the individual fields in the schema:
const productSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
category: {
type: String,
required: true,
enum: ['Generic', 'Smartphones', 'Laptops', 'Tablets', 'Accessories', 'Wearables'],
default: 'Generic'
},
mainImage: {
type: String,
required: true
},
images: {
type: [String], // Array of strings for multiple images
},
discount: {
type: Number,
default: 0, // Default discount is 0%
min: 0,
max: 100
},
isDiscounted: {
type: Boolean,
default: false // Default is not discounted
},
isFeatured: {
type: Boolean,
default: false // Default is not featured
},
isRecommended: {
type: Boolean,
default: false // Default is not recommended
},
isDailyDeal: {
type: Boolean,
default: false // Default is not a daily deal
},
newArrivals: {
type: Boolean,
default: false // Default is not a new arrival
}
}, {
timestamps: true
});
- Description: A brief explanation of the product (required).
- Price: The product's price (required).
- Category: Defines the product's type (e.g., Smartphones, Laptops, etc.). This is constrained with an enum to limit categories to predefined values.
- Main Image: A primary image for the product (required).
- Images: Allows for multiple images related to the product.
- Discount: A percentage discount to be applied to the product. If not specified, defaults to 0%.
- isDiscounted: A boolean flag indicating whether the product is discounted. This is automatically set based on the
discountfield. - isFeatured: A flag to mark products that are featured on the homepage or promotions.
- isRecommended: Marks products that are recommended for the user.
- isDailyDeal: A flag for products that are part of a daily deal.
- NewArrivals: Marks products that are newly added to the catalog.
Step 2 Middleware to Set isDiscounted Automatically
One of the key features of this product model is the use of middleware. Specifically, the pre-save middleware automatically updates the isDiscounted field when a discount is applied. This ensures that the flag correctly reflects the status of a product without the need for additional logic in the controller.
Here's the middleware in the schema:
// Middleware pre-save to set isDiscounted based on the discount value
productSchema.pre('save', function(next) {
this.isDiscounted = this.discount > 0;
next();
});
The middleware checks if the discount is greater than zero before saving the product. If it is, the isDiscounted flag is set to true; otherwise, it remains false.
Step 3- Controller: Get Discounted Products
Once the isDiscounted property is set correctly by the middleware, we can use it to query the database for discounted products. Here's the getDiscountedProducts controller that fetches products with isDiscounted: true:
getDiscountedProducts: async (req, res) => {
try {
// Fetch all products where isDiscounted is true
const discountedProducts = await Product.find({ isDiscounted: true });
// If no discounted products are found, return a 404
if (discountedProducts.length === 0) {
return res.status(404).json({ message: 'No discounted products found' });
}
// Return the discounted products
res.json(discountedProducts);
} catch (error) {
console.error('Error fetching discounted products:', error);
res.status(500).json({ message: error.message });
}
}
Explanation of the Controller:
Product.find({ isDiscounted: true }): This query searches for all products where theisDiscountedfield istrue, meaning the product has a discount applied.- Error handling: If no discounted products are found, it returns a 404 error with a relevant message. If there's an issue during the process, it returns a 500 error with the message of the exception.
- Response: If discounted products are found, they are returned in the response as a JSON array.
Step 4 - Product Creation Form: Empowering Administrators to Manage Store Inventory
Additionally, I created a form to facilitate the creation of new products by administrators. This form allows them to specify product details, including discount information. The form also categorizes products based on options selected by the administrator, ensuring they are displayed in the appropriate section of the store. Here's the form used for product creation:
import { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import './Form.css';
const CreateProduct = () => {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [price, setPrice] = useState('');
const [category, setCategory] = useState('Smartphones');
const [mainImage, setMainImage] = useState('');
const [images, setImages] = useState(['']);
const [discount, setDiscount] = useState(0);
const [isFeatured, setIsFeatured] = useState(false);
const [isRecommended, setIsRecommended] = useState(false);
const [isDailyDeal, setIsDailyDeal] = useState(false);
const [isNewArrivals, setIsNewArrivals] = useState(false);
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
const product = { title, description, price, category, mainImage, images, discount, isFeatured, isRecommended, isDailyDeal, isNewArrivals };
try {
const response = await axios.post('http://localhost:5004/api/products', product, {
headers: { 'Content-Type': 'application/json' }
});
console.log('Product created:', response.data);
navigate('/products');
} catch (error) {
console.error('Error:', error);
}
};
const handleAddImage = () => setImages([...images, '']);
const handleRemoveImage = (index) => setImages(images.filter((_, i) => i !== index));
return (
<form onSubmit={handleSubmit}>
<div>
<label>Title:</label>
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} required />
</div>
<div>
<label>Description:</label>
<textarea value={description} onChange={(e) => setDescription(e.target.value)} required />
</div>
<div>
<label>Price:</label>
<input type="number" value={price} onChange={(e) => setPrice(e.target.value)} required />
</div>
<div>
<label>Category:</label>
<select value={category} onChange={(e) => setCategory(e.target.value)} required>
<option value="Smartphones">Smartphones</option>
<option value="Laptops">Laptops</option>
<option value="Tablets">Tablets</option>
<option value="Accessories">Accessories</option>
<option value="Wearables">Wearables</option>
<option value="Generic">Generic</option>
</select>
</div>
<div>
<label>Main Image URL:</label>
<input type="text" value={mainImage} onChange={(e) => setMainImage(e.target.value)} required />
</div>
<div>
<label>Additional Images URLs:</label>
{images.map((image, index) => (
<div key={index} className="image-input">
<input
type="text"
value={image}
onChange={(e) => {
const newImages = [...images];
newImages[index] = e.target.value;
setImages(newImages);
}}
required
/>
<button type="button" onClick={() => handleRemoveImage(index)}>
Remove
</button>
</div>
))}
<button type="button" onClick={handleAddImage}>
Add Image
</button>
</div>
<div>
<label>Discount (%):</label>
<input type="number" value={discount} onChange={(e) => setDiscount(e.target.value)} min="0" max="100" />
</div>
<div>
<label>Featured:</label>
<input type="checkbox" checked={isFeatured} onChange={(e) => setIsFeatured(e.target.checked)} />
</div>
<div>
<label>Recommended:</label>
<input type="checkbox" checked={isRecommended} onChange={(e) => setIsRecommended(e.target.checked)} />
</div>
<div>
<label>Daily Deal:</label>
<input type="checkbox" checked={isDailyDeal} onChange={(e) => setIsDailyDeal(e.target.checked)} />
</div>
<div>
<label>New Arrivals:</label>
<input type="checkbox" checked={isNewArrivals} onChange={(e) => setIsNewArrivals(e.target.checked)} />
</div>
<button type="submit">Create Product</button>
</form>
);
};
export default CreateProduct;
This form is used by administrators to create new products and categorize them appropriately, making it an essential part of the product management flow. It ensures that administrators can add all the necessary details, and it seamlessly integrates with the backend logic for product management.
Implementing the Shopping Cart System
The goal was to create a shopping cart system that allows users to add products and manage their cart items.
To achieve this, I developed a Mongoose model for cart items and several controllers to handle item retrieval and addition.
Step 1 - Item Model
First, I defined an Item model in MongoDB using Mongoose. This model represents individual items in the shopping cart.
javascriptCopiaModifica
import mongoose from 'mongoose';
const itemSchema = new mongoose.Schema(
{
title: { type: String, required: true },
price: { type: Number, required: true },
quantity: { type: Number, required: true },
user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
product_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true }
},
{ timestamps: true }
);
const Item = mongoose.model('Item', itemSchema);
export default Item;
What does this schema do?
- Each item has a title, price, and quantity.
- The
user_idfield associates the item with the cart owner. - The
product_idfield links the item to an existing product. timestamps: trueautomatically adds creation and update dates.
Step 2 - Controller: Retrieving Cart Items
A key feature of the cart is allowing users to view the items they have added. For this, I created the getItemsByUser controller.
getItemsByUser: async (req, res) => {
const { id } = req.params;
console.log('User ID received:', id);
try {
console.log(`Fetching items for user_id: ${id}`);
const items = await Item.find({ user_id: id });
console.log(`Items found: ${items.length}`);
res.json(items);
} catch (error) {
console.error("Error retrieving items:", error);
return res.status(500).json({ message: error.message });
}
}
🟪 How does it work?
- Reads the user's
idfrom the URL parameters. - Searches the database for all items with
user_idmatching the user’s ID. - Returns the found items in JSON format.
- If something goes wrong, it returns a 500 error with an error message.
Example Request:
GET /api/cart/1234567890
🡆 Returns all cart items for the user with ID 1234567890.
Step 3 - Controller: Adding an Item to the Cart
The cart also needs to add products. This is handled by the addItem controller.
addItem: async (req, res) => {
const { product_id, user_id, price, quantity } = req.body;
try {
// 1. Data validation
if (!product_id || !user_id || !price || !quantity) {
return res.status(400).json({ message: 'Missing required fields' });
}
// 2. Check if the product exists
const product = await Product.findById(product_id);
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
// 3. Check if the item is already in the cart
const existingItem = await Item.findOne({ user_id, product_id });
let item;
if (existingItem) {
// 4a. If the item exists, update the quantity
existingItem.quantity += quantity;
item = await existingItem.save();
} else {
// 4b. If the item does not exist, create it
item = await Item.create({
product_id,
user_id,
title: product.title,
price,
quantity
});
}
return res.status(201).json({
message: 'Item added to cart',
item
});
} catch (error) {
console.error("Error adding item:", error);
return res.status(500).json({ message: 'Internal Server Error' });
}
}
🟪 Code Breakdown:
- Validation:
- Ensures all required data (
product_id,user_id,price,quantity) is provided. - If any field is missing, returns a 400 error.
- Ensures all required data (
- Product verification:
- Searches for the product in the database using
product_id. - If the product does not exist, returns a 404 error.
- Searches for the product in the database using
- Checks if the item is already in the cart:
- Searches for an item with the same
user_idandproduct_id.
- Searches for an item with the same
- If the item is already in the cart:
- Updates the quantity instead of creating a duplicate.
- If the item is not in the cart:
- Creates a new entry with title, price, and quantity.
- Response:
- Returns a success message with the updated or created item.
Example Request:
POST /api/cart
{
"product_id": "65abc123def456",
"user_id": "789xyz987abc",
"price": 299.99,
"quantity": 1
}
🡆 If the product is already in the cart, it updates the quantity.
🡆 If the product is not in the cart, it adds it.
Conclusion
With this implementation, the cart:
🟪 Allows users to view the items they have added.
🟪 Prevents duplicates, updating the quantity when necessary.
🟪 Ensures that each item is linked to a valid user and product.
The next step will be developing the cart UI in React and integrating Stripe for payments, which we will cover in the next challenge.
Managing Cart Item Count Across Multiple React Components
In this project, we are working on an ecommerce application where the cart item count needs to be managed and shared across multiple React components. The components involved include: App.tsx: Acts as the main container, where the cart item count is maintained and passed down as a prop. Navbar: Displays the current item count in the cart. Product Details: Allows users to add products to the cart. Cart: Displays the contents of the cart and allows users to modify the quantity of items. The main challenge lies in ensuring that the cart item count is consistent across all these components, especially since various actions (such as adding items to the cart or modifying quantities) can trigger changes in the count. Additionally, since the cart count is dependent on data fetched from an API, we need to handle updates in real time while maintaining smooth user experience and data integrity. In this project, I’m working on an ecommerce application where the cart item count needs to be managed and shared across multiple React components. The components involved include: App.tsx: Acts as the main container, where the cart item count is maintained and passed down as a prop. Navbar: Displays the current item count in the cart. Product Details: Allows users to add products to the cart. Cart: Displays the contents of the cart and allows users to modify the quantity of items. The main challenge is ensuring that the cart item count is consistent across all these components, especially since various actions (such as adding items to the cart or modifying quantities) can trigger changes in the count. Additionally, since the cart count depends on data fetched from an API, I need to handle updates in real time while maintaining a smooth user experience and data integrity.
To solve this challenge, I decided to use React’s useState hook in combination with props to manage and share the cart item count. The App.tsx component holds the state for the cart count and passes it down to the child components (Navbar, ProductDetails, Cart) as props. To update the cart item count, I fetch the current cart items from an API that retrieves the user's cart based on their session. The count is recalculated by summing up the quantities of items in the cart, and the updated count is reflected across the components in real time. Now, let’s take a deeper look at how this solution is implemented.
Using App.tsx as the Main Container for Cart Item Count Management
The App.tsx component manages the cartItemCount state using React's useState hook. The state is updated by calling the updateCartItemCount function, which fetches the user's cart items from the backend API. The function performs an HTTP GET request to the server, where we retrieve the items for the logged-in user. Once the items are fetched, we calculate the total count by summing the quantities of all items in the cart.
const [cartItemCount, setCartItemCount] = useState(0);
const updateCartItemCount = async () => {
const userId = sessionStorage.getItem('userId');
if (userId) {
try {
const response = await axios.get(`http://localhost:5004/api/items/items/user/${userId}`, {
withCredentials: true,
});
if (Array.isArray(response.data)) {
const totalItemCount = response.data.reduce((acc, item) => acc + item.quantity, 0);
setCartItemCount(totalItemCount);
}
} catch (error) {
console.error('Error fetching cart items:', error);
}
}
};
useEffect(() => {
updateCartItemCount();
}, []);
Controller (getItemsByUser):
The backend controller, getItemsByUser, handles the request to fetch the cart items for a given user. It takes the user ID from the URL parameters, queries the database for items associated with that user, and returns them as a JSON response.
getItemsByUser: async (req, res) => {
const { id } = req.params;
try {
const items = await Item.find({ user_id: id });
res.json(items);
} catch (error) {
return res.status(500).json({ message: error.message });
}
}
This solution ensures that the cart item count is always up-to-date and reflects the current state of the user's cart across all components that need it.
Next, we can go over how we manage the cart item updates from other components like ProductDetails and Cart, and ensure that changes in item quantities are reflected in real time.
Displaying Cart Item Count in the Navbar
Here’s the extracted part of the Navbar component that handles the cart item count:
import { FiShoppingCart } from 'react-icons/fi';
import PropTypes from 'prop-types';
const Navbar = ({ cartItemCount }) => {
return (
<div className="navbar">
<ul>
{isLoggedIn && (
<li>
<NavLink to="/cart">
<FiShoppingCart />
<span> Cart {cartItemCount || 0}</span>
</NavLink>
</li>
)}
</ul>
</div>
);
};
Navbar.propTypes = {
cartItemCount: PropTypes.number.isRequired,
};
export default Navbar;
In this snippet, the cartItemCount prop is passed to the Navbar component, which is then displayed in the cart icon area. If cartItemCount is 0 or undefined, it defaults to 0. This part is crucial because it ensures that the Navbar always reflects the current number of items in the cart, which is managed and passed down from the App.tsx component.
Managing Product Details and Cart Item Count in the Product Page
In this section, we will discuss the ProductDetails component, which handles the display of details for a single product and interacts directly with the cart item count. This component is essential for managing user actions related to adding products to the cart and displaying the product's detailed information.
Component Structure
The ProductDetails component consists of several key sections:
- Displaying Product Images:
- The component allows the user to view the main image of the product, which can be changed by clicking on the thumbnails of additional images. This is useful for showcasing the product from different angles or variants.
<div className="product-thumbnails">
<img
src={product.mainImage}
alt={`${product.title} main`}
className="product-thumbnail"
onClick={() => setMainImage(product.mainImage)}
/>
{product.images.map((image, index) => (
<img
key={index}
src={image}
alt={`${product.title} thumbnail ${index + 1}`}
className="product-thumbnail"
onClick={() => setMainImage(image)}
/>
))}
</div>
2) Product Information:
- This section displays the title, description, category, and price of the product. If there is a discount, both the original price and the discounted price are shown.
<div className="product-info">
<h1 className="product-title">{product.title}</h1>
{product.discount > 0 ? (
<div>
<p className="product-price original-price">${product.price.toFixed(2)}</p>
<p className="product-price discounted-price">${discountedPrice.toFixed(2)}</p>
</div>
) : (
<p className="product-price">${product.price.toFixed(2)}</p>
)}
<p className="product-description">{product.description}</p>
<p className="product-category">Category: {product.category}</p>
</div>
3) Quantity Control and "Add to Cart" Button:
- The component includes a numeric input that allows the user to select the quantity of the product to add to the cart. If the user is logged in, they can also click the "Add to Cart" button to add the product with the selected quantity to the cart. If the user is not logged in, a warning message will be shown.
<div className="product-buttons">
{isLoggedIn && (
<>
<input
type="number"
value={quantity}
min="1"
step="1"
onChange={handleQuantityChange}
/>
<button className="add-to-cart-button" onClick={handleAddToCart}>
Add to Cart
</button>
</>
)}
{isAdmin && (
<>
<button className="edit-product-button" onClick={() => navigate(`/update-product/${id}`)}>
Edit
</button>
<button className="delete-product-button" onClick={() => navigate(`/delete-product/${id}`)}>
Delete
</button>
</>
)}
</div>
Now, let’s focus on the cart item count interaction.
Interacting with the Cart Item Count
The main goal of the ProductDetails component is to allow users to add products to their cart. Once a product is added, it needs to update the global cart item count, which is shared with the Navbar component.
Core Logic for Cart Item Count
Here’s a breakdown of the logic involved in managing the cart item count:
1) Quantity Selection:
- The user can change the quantity of the product they wish to add to the cart by modifying the value in the input field.
const handleQuantityChange = (e) => {
const newQuantity = Number(e.target.value);
if (newQuantity > 0) setQuantity(newQuantity);
};
2) Adding the Product to the Cart:
- When the "Add to Cart" button is clicked, the
handleAddToCartfunction is triggered. This function first checks if the user is logged in. If not, it alerts the user to log in.
const handleAddToCart = async () => {
if (!userId) {
alert("Please log in to add items to your cart.");
return;
}
// continue with adding product to cart...
};
3) API Request to Add Item:
- If the user is logged in, the product is added to the cart by making a
POSTrequest to the server, sending theproduct_id,price,quantity, anduser_id. Once the item is added, the cart is updated.
const response = await axios.post(
'http://localhost:5004/api/items/add',
{
product_id: id,
price: product.price,
quantity,
user_id: userId
},
{ withCredentials: true }
);
4)Updating the Cart Item Count:
- After successfully adding the product, the component makes a
GETrequest to retrieve all items in the user's cart. The total number of items in the cart is calculated by summing up the quantities of each item. This value is then passed back to the Navbar component via thesetCartItemCountfunction.
const cartResponse = await axios.get(`http://localhost:5004/api/items/items/user/${userId}`, {
withCredentials: true,
});
const totalItemCount = cartResponse.data.reduce((acc, item) => acc + item.quantity, 0);
setCartItemCount(totalItemCount);
5)Reset Quantity and Show Message:
- After updating the cart item count, the product quantity input is reset to
1, and a success message is shown for a brief period.
setQuantity(1);
setTimeout(() => {
setMessage('');
}, 3000);
Controllers for Managing Cart Items
Now, let's analyze two controllers that are invoked within the component we just examined, which are responsible for handling cart items. These controllers interact with the backend to manage the shopping cart functionality, specifically adding items to the cart and updating the item count. Understanding these controllers will help us better connect the frontend behavior with the backend logic, ensuring the cart item count is accurate and up-to-date.
1)getItemsByUser – Fetch Items in Cart
This controller retrieves all the items for a specific user by their ID. It’s useful for updating the cart item count on the front end after a product is added. It returns a list of items, which can be used to calculate the total count of items in the cart.
1) getItemsByUser – Fetch Items in Cart
This controller retrieves all the items for a specific user by their ID. It’s useful for updating the cart item count on the front end after a product is added. It returns a list of items, which can be used to calculate the total count of items in the cart.
const items = await Item.find({ user_id: id });
Analysis: This query is pulling all the items associated with the user_id. In the ProductDetails component, you use this data to update the item count (totalItemCount) after adding a product to the cart.
2. addItem – Add an Item to Cart
This controller handles adding items to the shopping cart. It first checks if the item already exists in the cart (by product ID and user ID). If it exists, the quantity is updated; otherwise, a new item is added.
const existingItem = await Item.findOne({ user_id, product_id });
if (existingItem) {
existingItem.quantity += quantity;
item = await existingItem.save();
} else {
item = await Item.create({
product_id,
user_id,
title: product.title,
price,
quantity
});
}
Analysis: This controller is crucial for the handleAddToCart function in the ProductDetails component, where you send the POST request to add the item. The response from this controller confirms that the item was successfully added to the cart, and it triggers an update in the item count.
3. updateItem – Update Item Quantity in Cart
This controller is used to update the quantity of an existing item in the cart.
const item = await Item.findByIdAndUpdate(id, { quantity }, { new: true });
Analysis: While it's not directly used in the current implementation of the ProductDetails component, this controller is relevant if you decide to add functionality to update item quantities in the cart later. For now, it's useful to understand how the item data can be modified if needed.
How These Controllers Work Together:
addIteminteracts directly with theProductDetailscomponent when you add an item to the cart. It checks if the item is already in the cart and either creates or updates the item accordingly.getItemsByUseris used in theProductDetailscomponent to calculate the total count of items after adding a new item to the cart.updateItemcould be part of future enhancements if you allow users to modify the quantity of items in their cart. For now, thehandleAddToCartmethod only increases the item count by adding the item with a specified quantity.
Let me know if you'd like further details or to go over the data flow between the frontend and these controllers!
The Item Cart Component
n this section, we will analyze the Item Cart Component, which is responsible for managing the cart page functionality in the application. The component is structured to handle multiple parts, including fetching cart items, updating item quantities, deleting items, and processing the checkout. The component interacts with the backend through several API calls, ensuring the cart is up-to-date with the user’s selections.
Structure of the Cart Component:
- State Management:
items: Holds the array of items in the cart.loading: Indicates whether data is being fetched.error: Captures any errors during data fetching or processing.
- Fetching Cart Items:
- The component makes an API call to fetch items associated with the logged-in user. It calculates the total item count and updates the cart count using the
setCartItemCountprop.
- The component makes an API call to fetch items associated with the logged-in user. It calculates the total item count and updates the cart count using the
- Handling Item Quantity Changes:
- The component allows users to update the quantity of an item in their cart. When the quantity is modified, the component recalculates the total item count and updates the state.
- Handling Item Deletion:
- Users can remove items from their cart. The component handles the item removal by updating the
itemsstate and recalculating the total item count.
- Users can remove items from their cart. The component handles the item removal by updating the
- Checkout:
- The checkout process involves calculating the total price, initiating the payment through Stripe, and clearing the cart upon successful payment.
Focus on Item Count:
The item count functionality is central to the cart experience, ensuring that the user is informed of how many items are currently in their cart. The relevant part of the component that handles this functionality is within the useEffect hook and the functions that modify cart data:
- Fetching the Cart Items:
const fetchCartItems = async () => {
try {
const response = await axios.get(`http://localhost:5004/api/items/items/user/${userId}`);
if (response.status === 200 && response.data.length > 0) {
setItems(response.data);
const totalItemCount = response.data.reduce((acc, item) => acc + item.quantity, 0);
setCartItemCount(totalItemCount); // Updates the item count
} else {
setItems([]);
}
} catch (error) {
setError(error.message);
}
};
- Explanation: When the component is loaded, it fetches the cart items using the user's ID. It then calculates the total quantity of items in the cart by summing the quantity of each individual item. This total is passed to the
setCartItemCountfunction to update the item count in the parent component.
Handling Quantity Updates:
const handleQuantityChange = async (itemId, newQuantity) => {
if (newQuantity < 1) return;
try {
const response = await axios.put(`http://localhost:5004/api/items/${itemId}`, { quantity: newQuantity });
if (response.status === 200) {
const updatedItem = response.data.item;
const updatedItems = items.map(item => item._id === itemId ? updatedItem : item);
setItems(updatedItems);
const totalItemCount = updatedItems.reduce((acc, item) => acc + item.quantity, 0);
setCartItemCount(totalItemCount); // Recalculates item count after quantity change
}
} catch (error) {
setError(error.message);
}
};
- Explanation: When the quantity of an item is updated, this function makes an API call to update the quantity in the database. It then updates the local state (
items) and recalculates the total item count using the same method (reduce()) to ensure the item count remains accurate.
Handling Item Deletion:
const handleDelete = async (itemId) => {
if (window.confirm("Are you sure you want to remove this item from your cart?")) {
try {
await axios.delete(`http://localhost:5004/api/items/${itemId}`);
const updatedItems = items.filter(item => item._id !== itemId);
setItems(updatedItems);
const totalItemCount = updatedItems.reduce((acc, item) => acc + item.quantity, 0);
setCartItemCount(totalItemCount); // Recalculates item count after deletion
} catch (error) {
setError(error.message);
}
}
};
- Explanation: When an item is removed from the cart, the component filters out the deleted item from the
itemsstate. The total item count is then recalculated to reflect the change.
- Explanation: When an item is removed from the cart, the component filters out the deleted item from the
In summary, the item count is updated dynamically every time the cart state changes—whether it's fetching items, updating quantities, or deleting items—ensuring that the user’s cart is always accurate.
In-Depth Analysis of Cart Controllers and Their Impact on Item Count
Managing the shopping cart effectively requires precise backend logic to handle adding, updating, deleting, and clearing items. In this analysis, I will break down each controller, explaining its role and its direct influence on the total item count in the cart.
1. Retrieving Items in the Cart
Controller: getItemsByUser
getItemsByUser: async (req, res) => {
const { id } = req.params;
try {
const items = await Item.find({ user_id: id });
res.json(items);
} catch (error) {
return res.status(500).json({ message: error.message });
}
},How It Affects Item Count:
This controller retrieves all items associated with a specific user. On the frontend, the total item count is calculated by summing the quantity values of each item retrieved. If no items are found, the count remains zero.
2. Adding an Item to the Cart
Controller: addItem
addItem: async (req, res) => {
const { product_id, user_id, price, quantity } = req.body;
try {
if (!product_id || !user_id || !price || !quantity) {
return res.status(400).json({ message: 'Missing required fields' });
}
const product = await Product.findById(product_id);
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
const existingItem = await Item.findOne({ user_id, product_id });
let item;
if (existingItem) {
existingItem.quantity += quantity;
item = await existingItem.save();
} else {
item = await Item.create({
product_id,
user_id,
title: product.title,
price,
quantity
});
}
return res.status(201).json({ message: 'Item added to cart', item });
} catch (error) {
return res.status(500).json({ message: 'Internal Server Error' });
}
},How It Affects Item Count:
- If the product is already in the cart, its
quantityincreases, raising the total item count accordingly. - If the product is new, a new entry is created, adding its
quantityto the total count. - This controller ensures that the item count is updated dynamically based on user actions.
3. Updating an Item’s Quantity
Controller: updateItem
updateItem: async (req, res) => {
const { id } = req.params;
const { quantity } = req.body;
try {
const item = await Item.findByIdAndUpdate(
id,
{ quantity },
{ new: true }
);
if (!item) {
return res.status(404).json({ message: 'Item not found' });
}
res.status(200).json({ message: 'Item updated', item });
} catch (error) {
res.status(500).json({ message: error.message });
}
},How It Affects Item Count:
- This controller modifies the
quantityof an existing item. - Increasing the quantity will increase the total item count.
- Decreasing it will reduce the count but cannot go below one.
- On the frontend, after a successful update, the new total count is recalculated by summing up all item quantities.
4. Removing an Item from the Cart
Controller: deleteItem
deleteItem: async (req, res) => {
const { id } = req.params;
try {
const item = await Item.deleteOne({ _id: id });
if (item.deletedCount > 0) {
res.status(204).json({ message: 'Item deleted' });
} else {
return res.status(404).json({ message: 'Item not found' });
}
} catch (error) {
return res.status(500).json({ message: error.message });
}
},How It Affects Item Count:
- This controller removes an item completely from the cart.
- The frontend recalculates the item count by excluding the deleted item’s quantity.
- If the item was the only one in the cart, the count becomes zero.
5. Clearing the Cart After Payment
Controller: clearCartByUser
clearCartByUser: async (req, res) => {
const { id } = req.params;
try {
const result = await Item.deleteMany({ user_id: id });
if (result.deletedCount > 0) {
res.status(200).json({ message: 'Cart successfully cleared' });
} else {
res.status(404).json({ message: 'No items found in the cart' });
}
} catch (error) {
res.status(500).json({ message: 'Error clearing the cart', error: error.message });
}
},How It Affects Item Count:
- This controller deletes all items belonging to a user after a successful payment.
- The frontend must update the state to reflect an empty cart, setting the total item count to zero.
Final Thoughts
The cart functionality is fully dependent on these controllers. Their correct implementation ensures that: 🟪 The total item count remains accurate at all times. 🟪 The cart updates dynamically when users add, remove, or modify items. 🟪 The database and frontend remain in sync, preventing any inconsistencies.
By understanding how these controllers interact, I can ensure a smooth user experience where every action on the cart is immediately reflected in the UI. If necessary, I can further optimize this logic for efficiency, such as by implementing WebSockets for real-time updates instead of relying on frequent API calls.