Search code examples
javascriptreactjsreact-state

UseState React hook is changing variable, but not re-rendering


I'm working on creating a shopping cart. The cart itself (the list of all items in the cart) is a component and each cart item is a component. In each cart item, there's a dropdown list to change the quantity of that item you'd like to buy (1-5). There is also an X button that will remove that item from the cart. The X button (removing the item) works just fine. Here's the code for that:

  function removeItem(id) {
    const newCart = cart.filter((item) => item.id !== id)
    setCart(newCart);
  }

This is how I'm removing an item from the cart (creating a newCart variable and using setCart(newCart) to update that. This code, again, works just fine and the cart item is removed from the user's screen.

However, I'm trying to incorporate a similar approach to adjusting the quantity of an item. When I run the code below, however, the visual updates are not happening. However, when I console.log() the cart variable, the updates are shown there. Here is the code I have for that:

  function changeQuantity(event, id) {
    let newCart = cart;
    for (let i=0; i<cart.length; i++) {
      if (cart[i].id === id) {
        newCart[i].quantity = parseInt(event.target.value); // update quantity
        newCart[i].name = "CHANGED"; // debugging purposes, to know if it was changed
      }
    }
    setCart(newCart); // this doesn't seem to change the cart visually
    console.log(cart); // but in the console I can see the changes being made
  }

For some reason, when I go into the console, I can see the changes being made (the price is adjusted to the correct quantity, and the name of the item is "CHANGED"); but visually in the cart, this change is not shown. The removal of an item works fine, but using the same approach is not working to adjust the quantity.

I'm not sure where the logic is going wrong here, so any help is appreciated.

EDIT: Something interesting that happens is that when I save the code and the page is re-rendered, everything is updated accordingly, even it wasn't before.


Solution

  • React operates under the assumption that state is immutable. This makes it very quick for react to check whether the state has changed, because it just needs to do a single Object.is check (similar to ===).

    In your code, you are mutating the cart object, then calling setCart with the mutated object. React compares the old object with the new object using Object.is, and sees that they are the same object. As such, it appears nothing has changed and react does not rerender.

    Instead, you must make a new state. For example:

    let newCart = [...cart]; // make a shallow copy of the array
    for (let i=0; i<cart.length; i++) {
      if (cart[i].id === id) {
        newCart[i] = {
          ...newCart[i], // make a copy at this level too.
          quantity: parseInt(event.target.value),
          name: "CHANGED",
        }
      }
    }
    setCart(newCart);
    

    PS:

    console.log(cart); // but in the console I can see the changes being made
    

    cart is the old state, not the new state. So the reason you're seeing the changes is that you're mutating the old state. If you're doing things correctly, this log statement should show no change.