I'm currently trying to develop a dashboard application in React.js. I'm trying to fill an array of objects and map them in a list.
For every object I have to make an axios GET call to my server in order to get all the data. When I try to read the objects from the array I get a different order than I'm supposed to. The preferred output is that the objects in the array are sorted by DateTime.
My guess is that it has to do with threading and that the first completed request will be inserted before the others.
Does anyone know if there is a better way to what I'm doing now? Or if I'm forgetting something.
This is the main component where I perform my logic
const [shipments, setShipments] = useState([]);
async function createRows(shipmentId){
// The incoming id's are in correct order
const details = await getShipmentDetails(shipmentId);
// From this point on the responses are in a different order each time it's ran
let row = {
shipmentId: details.shipmentId,
shipmentReference: details.shipmentReference,
shipmentDateTime: details.shipmentDateTime,
shipmentItems: details.shipmentItems,
}
if(shipments.map(s => s.shipmentId).indexOf(row.shipmentId) == -1){
setShipments(rows => [...rows, row])
}
}
async function retrieveShipments(){
const amount = 5;
await connectToBol();
let allShipments = await getShipments();
allShipments.splice(amount, (allShipments.length-amount + 1))
if(allShipments.length > 0){
allShipments.forEach(async shipment => {
await createRows(shipment.shipmentId);
})
}
}
useEffect(() => {
retrieveShipments()
}, []);
This is the method that performs the axios request
export async function getShipmentDetails(shipmentId){
const response = await axios.get(`${baseUrl}/shipments/${shipmentId}`,{
headers: {
'Authorization': 'Bearer ' + <token>
}
})
.catch((error) => {
console.log('Error: ', error);
});
return response.data;
}
I'm not going to focus on how to solve your problem, I'm going to focus on the question asked in the title if that's okay:
Why am I getting a random ordered array?
Well, here's the situation: you're using a forEach
and making a bunch of api calls
allShipments.forEach(async shipment => {
await createRows(shipment.shipmentId);
})
It may not be obvious, but just because you've used await
in a .forEach
loop does NOT mean that each item is processed in order - in fact, what's happening is all of the createRows
calls are queued synchronously.
If I do [1,2,3].forEach(v => someAsyncFunc(v));
, it calls someAsyncFunc(1)
and then someAsyncFunc(2)
and then someAsyncFunc(3)
, but 2 and 3 are called without waiting for 1 to finish.
So, if by some miracle someAsyncFunc(2)
finishes before someAsyncFunc(1)
does, you may end up with out-of-order results. That's what I believe is happening to you, and why you're asking this question.
If you want someAsyncFunc(2)
to run AFTER someAsyncFunc(1)
finishes, you don't do a forEach
loop, you do a for...of
loop
for (let x of [1,2,3]) {
await someAsyncFunc(x);
}
Then, 2 will start only after 1 finishes.