skip to Main Content

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


  1. I think your addToCart function is tricky. You should avoid mutating the cart state directly in your addToCart function.

    For example:

    function addToCart(itemID) {
      const newCartItem = cart.find((cartItem) => cartItem.id == itemID);
    
      if (newCartItem === undefined) {
        setCart([...cart, { id: itemID, quantity: 1 }]);
      } else {
        setCart((prevCart) =>
          prevCart.map((cartProduct) =>
            cartProduct.id == itemID
              ? { ...cartProduct, quantity: cartProduct.quantity + 1 }
              : cartProduct
          )
        );
      }
    }
    

    You should avoid using the same id attribute for both the div and the button elements in your mapShoppingMenu function. The id attribute should be unique for each element in the document. You can use other attributes such as data-id or name to store the item id for the button element, and then access them using e.target.dataset.id or e.target.name in your onClick handler.

    <div className="item-card">
      <img src={shoppingItem.image} alt="" className="item-image" />
      <h2>{shoppingItem.name}</h2>
      <h3>${shoppingItem.price}</h3>
      <AddToCart />
      <button
        data-id={shoppingItem.id}
        onClick={(e) => {
          let itemID = getItemID(e.target.dataset.id);
          console.log(itemID);
          addToCart(itemID);
        }}
      >
        Add To Cart
      </button>
    </div>
    
    Login or Signup to reply.
  2. 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 of cart 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) after setCart, the value logged into the console will be the previous value, not the new one. But if you render the value of cart you will see that it has been updated.

    Here is a quick example to illustrate : Let’s say that i have a counter component :

    function Counter() {
      const [count, setCount] = useState(0);
    
      const handleClick = () => {
        setCount(count + 1); // State update is scheduled asynchronously
        console.log(count); // This may not show the updated count immediately
      };
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={handleClick}>Increment</button>
        </div>
      );
    }
    

    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 !

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search