forEach() does not update the values inside a database call

Viewed 275

The snippet bellow represents a listener to the database which on change returns an number of objects (each object represents a document) then it will get the id and the quantity of the object then it will loop through each object and will add object.price element to each object with another call to the database then it will use the quantity an the object.price element to get the total amount of each object but the issue is that the quantity and the id does not get updated for each object as expected instead of that the quantity value will always be the value of the last object quantity. but When I tried to get the values of the id and quantity inside the forEach() but once inside the function that makes the call the database and once outside of it as shown in the snippet bellow the issue was happening only inside the function and the same problem appears when trying to get the quantity value after the function(I will put a copy of what has been logged in the console after the snippet)

  useEffect(() => {
   
    db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket").onSnapshot((docs) => {
        const oneSubTotal = [];
        let array = [] 
        let object = undefined;
        let id = undefined;
        let quantity = undefined;
        docs.forEach(  doc =>{
                object = doc.data()
                id = object.id;
                quantity = object.quantity
                
          
                console.log( " the quantity  before is " + quantity)
                console.log(" the id before is " + object.id)
                db.collection("products").doc(id).get().then( (e)=>{
                    object.price =  (e.data().price);
                    console.log( " the quantity inside is " + quantity)
                    console.log(" the id inside is " + id)
                    oneSubTotal.push(object.price * quantity)     
                     
                })
                    console.log( " the quantity after is " + quantity)
                    console.log(" the id after is " + id)
        })
  });
  }, [])

this is what has been logged in the console

 the quantity  before is 9
 the id before is 1
 the quantity  before is 10
 the id before is 2
 the quantity inside is 10
 the id after is 2
 the quantity inside is 10
 the id after is 2
 the quantity after is 10
 the id after is 2
 the quantity after is 10
 the id after is 2
1 Answers

Tl;dr:

  • Create a variable called promises before the loop and initialize it with an empty array: let promises = [];
  • Inside the loop, instead of executing the promise (by calling .then or await), push that promise to the promises array
  • Once outside the loop, call Promise.all(promises) to resolve all the promises pushed inside the promises array which will return an array of all the responses.

When you want to use a promise inside a for-each loop (or any loop for that matter), you shouldn't execute that promise when inside the loop. Instead, you should push it into an array of promises (which is outside the loop) and after the loop's end, resolve all of them at once using Promise.all. Like this:

useEffect(() => {
  db.collection("users")
    .doc("4sfrRMB5ROMxXDvmVdwL")
    .collection("basket")
    .onSnapshot((docs) => {
      const oneSubTotal = [];
      let array = [];
      let object = undefined;
      let id = undefined;
      let quantity = undefined;
      let promises = [];
      docs.forEach((doc) => {
        object = doc.data();
        id = object.id;
        quantity = object.quantity;
        console.log(" the quantity  before is " + quantity);
        console.log(" the id before is " + object.id);
        promises.push(db.collection("products").doc(id).get());
        console.log(" the quantity after is " + quantity); // quantity won't change here because we're not fulfilling the promise yet.
        console.log(" the id after is " + id); // same as above, this won't update before the promise is fulfilled, which is happening below
      });
      Promise.all(promises).then((allDocumentsFromForLoop) => {
        allDocumentsFromForLoop.map((document) => {
          console.log( " the quantity inside is " + quantity)
          console.log(" the id inside is " + document.data().id)
          oneSubTotal.push(document.data().price * quantity);
        });
      });
    });
}, []);
Related