Home > OS >  React setter function not updating state as expected
React setter function not updating state as expected

Time:01-02

I am somewhat new to React and I am running into an issue and I was hoping someone will be willing to help me understand why my method is not working.

I have this state:

const [beers, setBeers] = useState([
    {
      id: 8759,
      uid: "8c5f86a9-87bf-41fa-bc7f-044a9faf10be",
      brand: "Budweiser",
      name: "Westmalle Trappist Tripel",
      style: "Fruit Beer",
      hop: "Liberty",
      yeast: "1056 - American Ale",
      malts: "Special roast",
      ibu: "22 IBU",
      alcohol: "7.5%",
      blg: "7.7°Blg",
      bought: false
    },
    {
      id: 3459,
      uid: "7fa04e27-0b6b-4053-a26b-c0b1782d31c3",
      brand: "Kirin",
      name: "Hercules Double IPA",
      style: "Amber Hybrid Beer",
      hop: "Nugget",
      yeast: "2000 - Budvar Lager",
      malts: "Vienna",
      ibu: "18 IBU",
      alcohol: "9.4%",
      blg: "7.5°Blg",
      bought: true
    }]

I am rendering the beers with a map function and I have some jsx that calls a handleClick function

 <button onClick={() => handleClick(beer.id)}>
        {beer.bought ? "restock" : "buy"}
      </button>

this is the function being called:

 const handleClick = (id) => {
 
   setBeers((currentBeers) =>
      currentBeers.map((beer) => {
        if (beer.id === id) {
          beer.bought = !beer.bought;
          console.log(beer);
        }
        return beer;
      })
    );
    
  };

I wanted to use an updater function to update the state, I am directly mapping inside the setter function and since map returns a new array, I thought everything would work correctly but in fact, it doesn't. It works only on the first button click and after that it stops updating the value.

I noticed that if I use this method:

  const handleClick = (id) => {
    const newbeers = beers.map((beer) => {
      if (beer.id === id) {
        beer.bought = !beer.bought;
      }
      return beer;
    });

    setBeers(newbeers);
  };

Then everything works as expected.

Can someone help me understand why my first method isn't working?

CodePudding user response:

OK, I think I have figured it out. The difference between my sandbox and your sandbox is the inclusion of <StrictMode> in the Index file. Removing this fixes the issue, but is not the correct solution. So I dug a little deeper.

What we all missed was that in your code you were modifying the previous state object that is passed in. You should instead be creating a new beer object and then modifying that. So this code works (I hope):

setBeers((currentBeers) => 
  currentBeers.map((currentBeer) => { // changed beer to currentBeer
    const beer = {...currentBeer};
    if (beer.id === id) {
      beer.bought = !beer.bought;
    }
    return beer;
  )
});

I hope that this helps.

CodePudding user response:

react does not deeply compares the object in the state. Since you map over beers and just change a property, they are the same for react and no rerender will happen. You need to set the state with a cloned object.

e.g.:

import {cloneDeep} from 'lodash';

...

    setBeers(
      cloneDeep(currentBeers.map((beer) => {
          if (beer.id === id) {
            beer.bought = !beer.bought;
            console.log(beer);
          }
          return beer;
        })
      )
    );
  • Related