I am creating a food ordering application as project. Here, user can add items from a single restaurant only. If they tried adding items from another restaurant too, they will be shown an alert regarding, and given a choice, 1) Either cancel adding current item, or 2) It will clear your existing cart and add current item.
Suppose I have existing item A from restaurant A in cart and try adding item B from restaurant B in cart now.
Issue I am facing is that when I select option 2), it updates the itemID from A -> B, but restaurantID remains same as restaurant A only, it doesn’t update. In order to update restaurantID, I had to increase/ decrease the item quantity. It would update the restaurantID, but request send to backend will have stale quantity only. Attached associated codes and screenshot below, any help would be appreciated.
Cart-Context.js (frontend)
import { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
import { getCartAPI, updateCartAPI } from "../services/CartService";
import { AuthContext } from "./Auth-Context";
import { debounce } from "../util/debounce";
import { Alert } from "../components/UI/Alert";
export const CartContext = createContext({
items: [],
addToCart: () => {},
removeFromCart: () => {},
restaurantId: null,
setRestaurantId: null
});
export const CartCtxProvider = ({ children }) => {
const { token } = useContext(AuthContext);
const [cart, setCart] = useState({ items: [] });
const [restaurantId, setRestaurantId] = useState(null);
const [alert, setAlert] = useState(null)
let batchedCartUpdatesRef = useRef([]);
useEffect(() => {
const fetchCart = async () => {
try {
const loadedCart = await getCartAPI(token);
setCart({ items: loadedCart.items || [] });
setRestaurantId(loadedCart.restaurant ? loadedCart.restaurant._id : null);
}
catch (error) {
console.error("Failed to fetch cart:", error);
}
};
if(token){
fetchCart();
}
}, [token]);
const processBatchedUpdates = useCallback(debounce(async () => {
if (batchedCartUpdatesRef.current.length > 0) {
try {
await updateCartAPI(token, { updates: batchedCartUpdatesRef.current, restaurant: restaurantId });
batchedCartUpdatesRef.current = [];
}
catch (err) {
throw new Error("Failed updating cart");
}
}
}, 300), [token, restaurantId]);
const confirmHandler = () => {
setCart({ items: [{ id: alert.itemId, name: alert.name, quantity: 1, price: alert.price }] });
//Clear previous and add current
setRestaurantId(alert.currentRestaurantId);
batchedCartUpdatesRef.current = [{ type: 'clear'}, { type: 'add', itemId: alert.itemId, name: alert.name, price: alert.price }];
processBatchedUpdates();
setAlert(null)
}
const cancelHandler = () => {
setAlert(null)
}
const addToCart = ({ itemId, name, price, currentRestaurantId }) => {
//If there's no existing cart, create a new one
if (!restaurantId) {
setRestaurantId(currentRestaurantId);
setCart({ items: [{ id: itemId, name, quantity: 1, price }] });
batchedCartUpdatesRef.current = [{ type: 'add', itemId, name, price }];
}
//If switching restaurants, show alert
else if (restaurantId !== currentRestaurantId) {
setAlert({ itemId, name, price, currentRestaurantId });
}
// Add to existing cart
else {
addItemToCart({ itemId, name, price, currentRestaurantId });
}
};
const addItemToCart = ({itemId, name, price, currentRestaurantId }) => {
setCart((prevCart) => {
let newCart = [...prevCart.items];
let currentItemIdx = newCart.findIndex((item) => item.id === itemId);
let newItem;
if (currentItemIdx !== -1) {
newItem = {
...newCart[currentItemIdx],
quantity: newCart[currentItemIdx].quantity + 1
};
newCart[currentItemIdx] = newItem;
}
else {
newItem = {
id: itemId,
name,
quantity: 1,
price
};
newCart.push(newItem);
}
return {
items: newCart
};
});
if(restaurantId !== currentRestaurantId){
setRestaurantId(currentRestaurantId);
}
batchedCartUpdatesRef.current.push({ type: 'add', itemId, name, price });
processBatchedUpdates();
};
const removeFromCart = ({ itemId }) => {
setCart((prevCart) => {
const newCart = [...prevCart.items];
const currentItemIdx = newCart.findIndex((item) => item.id === itemId);
if (currentItemIdx !== -1) {
if (newCart[currentItemIdx].quantity > 1) {
newCart[currentItemIdx].quantity -= 1;
} else {
newCart.splice(currentItemIdx, 1);
}
}
return {
items: newCart
};
});
batchedCartUpdatesRef.current.push({ type: 'remove', itemId });
processBatchedUpdates();
};
const ctxValue = {
items: cart.items,
addToCart,
removeFromCart,
restaurantId,
setRestaurantId
};
return <CartContext.Provider value={ctxValue}>
{children}
{alert && (
<Alert onConfirm={confirmHandler} onCancel={cancelHandler}/>
)}
</CartContext.Provider>;
};
CartService.js (frontend)
export const getCartAPI = async (token) => {
const response = await fetch('http://localhost:3000/cart', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error("Failed to get cart");
}
const result = await response.json();
return result;
};
export const updateCartAPI = async (token, { updates, restaurant }) => {
const response = await fetch('http://localhost:3000/cart/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ updates, restaurant })
});
if (!response.ok) {
throw new Error("Failed to update cart");
}
const result = await response.json();
return result;
};
debounce.js (frontend)
//Function and wait as arguments to limit calling function in case no activity within "wait"
export const debounce = (func, wait) => {
let timeout;
return function(...args){
const context = this;
//Reset if activity happns
clearTimeout(timeout)
//Wait to call
timeout = setTimeout(() => func.apply(context, args), wait)
}
}
cartController.js (backend)
exports.updateCart = async (req, res, next) => {
try {
const { updates, restaurant } = req.body;
const user = req.user._id;
let cart = await Cart.findOne({ user });
if (!cart) {
cart = new Cart({ user, restaurant, items: [] });
}
else if (restaurant && restaurant !== cart.restaurant.toString()) {
cart.items = [];
cart.restaurant = restaurant;
}
updates.forEach(update => {
const { type, itemId, name, price } = update;
if (type === 'clear') {
cart.items = [];
cart.restaurant = restaurant;
}
else {
const itemIdx = cart.items.findIndex(cartItem => cartItem.item.toString() === itemId);
if (type === 'add') {
if (itemIdx !== -1) {
cart.items[itemIdx].quantity += 1;
}
else {
cart.items.push({ item: itemId, quantity: 1, name, price });
}
}
else if (type === 'remove') {
if (itemIdx !== -1) {
if (cart.items[itemIdx].quantity > 1) {
cart.items[itemIdx].quantity -= 1;
} else {
cart.items.splice(itemIdx, 1);
}
}
}
}
});
await cart.save();
res.status(200).json({ cart });
}
catch (err) {
next(err);
}
};
Look as I add item, it has previous restaurantID, but when I increase the quantity, it updates the restaurantID, but the quantity is stale. IMAGE 1 and IMAGE 2
I was unsure whether it occurred because of the logic I added for debouncing and batched updates. So, tried eliminating them, still issue persists.
Vidhi Mathur is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.