0

I'm using two chained firebase requests in my code. the first one is fetching all the reviews, then I iterate trough all the results, firing a second firebase request for each element in loop.

After the loop, I've updated the state with the new data using setState. And that update is making my GUI transparent, like so:

enter image description here

the bottom part is randomly transparent each time, sometimes the render is visible a bit, sometimes not at all. When I remove the setState block, everything is fine.

The code in question:

    getReviews() {
      let reviewData = {}
      let vendorId = this.props.item.vendor_id
      let product = this.props.item.key

      firebase.database().ref('u_products/' + vendorId + '/' + product + '/reviews/').once('value', (data) => {
        reviewData = data.val()
        if (!reviewData) {
          this.setState({
            loadedReviews: true
          })
          return
        } else {
          for (var key in reviewData) {
            firebase.database().ref('users/' + reviewData[key].poster_uid + '/').once('value', (data) => {
              let userData = data.val()
              if (userData) {
                this.getUserImageUrl()
                 ...
              }
            })
          }
          this.state.reviewData = reviewData
            this.setState({
              dataSource: this.state.dataSource.cloneWithRows(reviewData),
              loadedReviews: true,
            })
        }
      })
    }

Intended behaviour is first firebase request -> second one iterating for all the results from the first request ->setState.

Does anyone else have this issue?

Romy
  • 407
  • 6
  • 25
  • Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) –  Jun 13 '17 at 11:41
  • One of the most common questions of all time; your final `setState()` is executed way before the individual requests finish. You even have the potential solution: Promises, in your title, but I don't see you using any. –  Jun 13 '17 at 11:41
  • According to the Firebase docs, Their functions are Promises by them selves. I'm obviously new to promises, been trying to wrap my head around it. – Romy Jun 13 '17 at 12:51
  • @ChrisG but shouldn't the `for` loop "wait" until the firebase request inside is done before making the next iteration? – Romy Jun 13 '17 at 12:53
  • No, because the loop has no clue that the `.once()` call runs asynchronously. You have to provide a callback function because the operation started by the `.once()` call will finish at some arbitrary point later in time, and the callback contains further processing. The `.once()` itself however is done, and the loop proceeds. One way to solve this is to update the state inside the callback, where you have your `...` –  Jun 13 '17 at 14:30
  • @ChrisG I've tried updating state inside the callback, and that code is only executing for the last iteration which was weird to me. I can use conventional Promises with `resolve` and `reject`, but Firebase code doesn't have that. Could you do a simple "multiple firebase executions inside a loop" code snippet, I really feel that when I see a concrete example, I'll get a hang of it.. – Romy Jun 14 '17 at 08:28

1 Answers1

2

firebase's .once() returns a Promise, so what you need to do is create an array of Promises, then call Promise.all(array).then((arrayOfData) => { ... });

You can now process the resulting array, then call .setState()

Here's a mockup:

/* firebase mockup */
function Result(url) {
  this.url = url;
  this.once = function(param, callback) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        var data = {
          val: () => {
            return this.url + " query result";
          }
        };
        resolve(data);
      }, Math.random() * 500 + 1000);
    });
  };
}
var firebase = {
  database: function() {
    return firebase;
  },
  ref: function(url) {
    return new Result(url);
  }
};

var reviewData = {
  "test": {
    poster_uid: "testData"
  },
  "2nd": {
    poster_uid: "2ndData"
  }
};

// ACTUAL IMPORTANT CODE BELOW #################

var arrayOfPromises = [];
for (var key in reviewData) {
  arrayOfPromises.push(firebase.database().ref('users/' + reviewData[key].poster_uid + '/').once('value'));
}
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
  arrayOfResults.map((data, i) => {
    let userData = data.val();
    if (userData) {
      console.log(i, userData);
    }
  });
  console.log("setting state now");
});
  • Thank you! In the meantime I've made my code work by using the exact approach (putting all promises in array and then `promise.all()`. I will review your code and see if it has any improvements (it probably does :)) Now I have a concrete examples which I can expand for future use :) – Romy Jun 14 '17 at 11:55