Beginner to React here, currently learning how to build a shopping cart.
My addToCart() function has been passed as a function to an add-to-cart button onClick event handler, which is then supposed to update the cart array state via setCart.
The problem is, whenever I click the button (which is supposed to increment quantity), the cart array only updates on every second click (e.g. i click on Apple’s add to cart, and cart remains at {id: 1, quantity:2}
, and on the second click, it updates to {id:1, quantity:3}
.
Also, when I click on add to cart for Orange (which has an id of 3), the first click will increment cart id 1 (e.g. i end up with [{id: 1, quantity: 3}]
, while the second click gives [{id: 1, quantity: 3}, {id: 3, quantity: 1}]
.
Would appreciate any help. Thank you.
const shoppingList = [
{ id: 1, name: "Apple", image: apple, price: 1.5 },
{ id: 2, name: "Pear", image: pear, price: 2.5 },
{ id: 3, name: "Orange", image: orange, price: 3.5 },
{ id: 4, name: "Banana", image: banana, price: 4.5 },
{ id: 5, name: "Watermelon", image: watermelon, price: 5.5 },
];
export default shoppingList;
import shoppingList from "../Data/ShoppingList";
import { useState, useRef } from "react";
import AddToCart from "./AddToCart";
const ShoppingMenu = () => {
const [cart, setCart] = useState([{ id: 1, quantity: 2 }]);
function getItemID(id) {
const itemID = shoppingList.find((shoppingItem) => id == shoppingItem.id);
return itemID.id;
}
function addToCart(itemID) {
const newCartItem = cart.find((cartItem) => cartItem.id == itemID);
console.log({ newCartItem, cart });
if (newCartItem === undefined) {
const cartReplica = cart;
setCart([...cartReplica, { id: itemID, quantity: 1 }]);
console.log(cart);
} else {
console.log("exists");
const cartReplica = cart;
cartReplica.map((cartProduct) =>
cartProduct.id == itemID
? setCart([{ ...cartProduct, quantity: cartProduct.quantity + 1 }])
: cartProduct
);
console.log(cart);
}
}
const mapShoppingMenu = shoppingList.map((shoppingItem) => {
return (
<div id={shoppingItem.id} className="item-card">
<img src={shoppingItem.image} alt="" className="item-image" />
<h2>{shoppingItem.name}</h2>
<h3>${shoppingItem.price}</h3>
<AddToCart />
<button
id={shoppingItem.id}
onClick={(e) => {
let itemID = getItemID(e.target.id);
console.log(itemID);
addToCart(itemID);
}}
>
Add To Cart
</button>
{/* {console.log(cart)} */}
</div>
);
});
return <div className="shopping-list">{mapShoppingMenu}</div>;
};
export default ShoppingMenu;
Tried modifying addToCart, tried modifying the event handler for button..
2
Answers
I think your
addToCart
function is tricky. You should avoid mutating thecart
state directly in youraddToCart
function.For example:
You should avoid using the same
id
attribute for both thediv
and thebutton
elements in yourmapShoppingMenu
function. Theid
attribute should be unique for each element in the document. You can use other attributes such asdata-id
orname
to store the item id for the button element, and then access them usinge.target.dataset.id
ore.target.name
in your onClick handler.Actually, your code works fine. The problem is that you don’t understand how the state is updated in a functional component.
setCart
is asynchronous, meaning that the rest of your function will be executed before the value ofcart
changes. Why ? Because React doesn’t immediately update the state. Instead, it schedules the update to be processed later.So when you are calling
console.log(cart)
aftersetCart
, the value logged into the console will be the previous value, not the new one. But if you render the value ofcart
you will see that it has been updated.Here is a quick example to illustrate : Let’s say that i have a counter component :
If i click on "Increment", the logged value will be 0, but the value rendered will be 1, because when
console.log
is called,count
hasn’t been updated yet, but a few ms laters the component will be rerendered with the new value.I know that this can be a challenging topic for beginners in React, so if you have any questions, don’t hesitate to ask !