Welcome to programming with web APIs. You have a typical async problem here. I'll explain what's happening in your code, then give you first steps to a solution, point out some further considerations, and finally provide links with more information for you to read up on this incredibly common (but initially equally confusing) topic.
The problem: the download URLs are loaded asynchronously
Your code calls getDownloadURL
, which happens asynchronously. This means that your other code continues while the URL is being loaded. Then when the URL is loaded, your callback is called with the URL. This means that any code that needs the URL, must be inside the callback.
This is easiest to see if you simply the code and add some log statements:
console.log("Before starting getDownloadURL");
this.storage.ref().child(value.key + '.jpg').getDownloadURL().then(function (url) {
console.log("Got download URL");
});
console.log("After starting getDownloadURL");
When you run this code it prints:
Before starting getDownloadURL
After starting getDownloadURL
Got download URL
That is probably not the order you expected, but it completely explains why the array is empty when you print it: the URLs haven't been loaded yet, so they haven't been added to the array yet.
The solution: put all code that needs the download URL inside the callback
To solve this problem all code that needs the download URL from the database must be inside the callback (or be called from there). So a first step would be:
let tmp_array2 = [];
lodash.forEach(tmp_array, (value, key) => {
this.storage.ref().child(value.key + '.jpg').getDownloadURL().then(function (url) {
tmp_array2.push({ id: value.key, image: url });
this.setState({ image: tmp_array2 });
console.log(tmp_array2);
});
});
When you run this, you will see that it logs the array as often as there are child nodes/images. Each time it gets a download URL (remember: this happens asynchronously) it adds it to the array, and then tells React that the state has changed (to it can update the UI).
Some additional considerations and problems
There are more pitfalls in my last code snippet still:
- Calling
setState()
repeatedly may result in a UI that is only partially updated, or in flicker. In that case, consider checking if you've gotten all download URLs before calling setState
with something like: if (tmp_array2.length === tmp_array.length) this.setState({ image: tmp_array2 });
.
- The download URL calls happen asynchronously, and may (I think, it depends on the backend implementation of that API) complete in a different order than in which you call them. In other words: the download URL for the second image may be returned before the download URL for the first image. If this is a concern for your application, be sure to either replicate the other information for the image in
tmp_array2
(I think you already do this), or consider storing the download URLs in a way that allows you to associate them back with the items in tmp_array
.
More info
As I said at the start: this is a very common problem for developers who are new to dealing with asynchronous APIs to run into. So there are probably hundreds more relevant questions/links if you search for them. Here are some of my favorites: