How to to setState in a loop?

Viewed 53

I want to loop through an array, and call a function for each item, which does setState. I tried the following code:

const approveOrder = uniqueId => {
    if (approvedList.indexOf(uniqueId) < 0) {
        setApprovedList(approvedList.concat(uniqueId));
    }
};

const approveAllOrders = data => {
    data.forEach(dataItem => {
        approveOrder(dataItem.unique_id);
    });
};

But after the above function approveAllOrders, my list approvedList only contains one item, which is the last item of my data, which I passed to the function. I know that setState is not instant, that's why it is happening. So, what can I do to make my code work?

3 Answers

You should use a functional state update so that multiple enqueued updates correctly update from the previous state.

const approveOrder = uniqueId => {
  if (approvedList.indexOf(uniqueId) < 0) {
    setApprovedList(approvedList => approvedList.concat(uniqueId));
  }
};

Alternatively you can filter/map your data by id's not contained in the current state, and update state once.

const approveAllOrders = data => {
    const newDataIds = data.filter(dataItem => {
        return approvedList.indexOf(dataItem.unique_id) < 0;
    }).map(({ unique_id }) => unique_id);
    setApprovedList(approvedList => [...approvedList, newDataIds]);
};

or

const approveAllOrders = data => {
    const newDataIds = data.filter(dataItem => {
        return !approvedList.includes(dataItem.unique_id);
    }).map(({ unique_id }) => unique_id);
    setApprovedList(approvedList => [...approvedList, newDataIds]);
};

You can first create a list of all the orders and then update the state. You don't need to have 2 functions to do this.

const approveAllOrders = data => {
    const orders = [];

    data.forEach(dataItem => {
       if (approvedList.indexOf(dataItem.unique_id) < 0) {
          orders.push(dataItem);
       }
    });

    setApprovedList([...approvedList, ...orders]);
};

you can also use reduce here.

const res = data.reduce( (res, dataItem) => {
   if(!res.includes(dataItem.unique_id)) {
       res.push(dataItem.unique_id)
   }
   return res
})

setApprovedList(res)

Related