S.M_Emamian
S.M_Emamian

Reputation: 17383

useState doesn't updated - nextjs

I'm using nextjs. I have a variable: delivery_times.

I created this with useState and it's empty at first time. then with useEffect function I called my api and I updated it.So far, so good. now I added a property called is_selected when use click on each object. default value is false when it changed to true, my element is added a selected class.

const [delivery_times,setDeliveryTimes] = useState([])
  
  useEffect(() => {
    getDeliveryTimes()
  }, []);

  const getDeliveryTimes = () =>{
    api.get('cart/delivery-times').then((res: any) => {
      setDeliveryTimes(res.delivery_times)
    }).catch(err => {
      console.log(err);
    })
  }

  const setSelectedDeliveryTime = (item) => {
    
    for(var i = 0 ; i < delivery_times.length ; ++i){
      delivery_times[i].is_selected = false;
    }
    
    const objIndex = delivery_times.findIndex((obj => obj.date == item.date));
    console.log(objIndex,item)
    delivery_times[objIndex].is_selected = true;

    setDeliveryTimes(delivery_times) //it doesn't work
  }


<div className="delivery-times mt-5">
  {delivery_times.map(item => {
    return (<div onClick={()=>setSelectedDeliveryTime(item)} className={item.is_selected === true?"item selected":"item"}>
      
      <p className="date">{item.time_range}</p>
    </div>
    )
  })}
</div>

but selected class doesn't added at all.

Upvotes: 0

Views: 1376

Answers (2)

choz
choz

Reputation: 17858

The reason being, as stated in their doc, React uses Object.is to compare the previous value of your old and new state. Therefore, when you mutated delivery_times and assign it back to state, react thought they were identical and therefore it doesn't bother to re-compute your state's value.

E.g.

const arr = [];
Object.is(arr, arr); // True
Object.is(arr, [...arr]); // False

As the previous answer, in order for your state to update, you'll need to update the state with a new array - There are multiple ways to clone an array, and spread is one of the most favorite, fastest and laziest way out there.

const setSelectedDeliveryTime = (item) => {
  // Create new array, copied from delivery_times;
  const new_delivery_times = [...delivery_times];

  for(var i = 0 ; i < new_delivery_times.length ; ++i){
    new_delivery_times[i].is_selected = false;
  }

  const objIndex = new_delivery_times.findIndex((obj => obj.date == item.date));
  new_delivery_times[objIndex].is_selected = true;

  setDeliveryTimes(new_delivery_times); // <-- notice it assigns new array.
}

Upvotes: 1

Nikita Mazur
Nikita Mazur

Reputation: 1785

Use state needs to change in order to trigger re-render, setDeliveryTimes(delivery_times) sets the same array, although, one of its elements value is changed. you can do this:

setDeliveryTimes([...delivery_times])

which should trigger the re-render

Upvotes: 3

Related Questions