Home > OS >  Returned array evaluated later from async Firestore calls doesn't update in React
Returned array evaluated later from async Firestore calls doesn't update in React

Time:12-04

It seems "images" array is first empty and the elements are added later when data is fetched from firestore - but the React state won't update, so no image is displayed, although I used await everywhere possible.

useEffect(() => {
    async function getAllImages() {
      const images = await imageStorage.downloadAllImages(true);
// images is first empty and then populated after the html has been rendered
      setImages(images);
    }
   
      getAllImages();
    
  }, []);

display images:

const handleGetImages = (images) => {
    console.log("images", images); // image is first empty and then populated after the html has been rendered
    return images.map((imageData) => {
      return (
        <Grid.Column key={imageData.url}>
          <span class="image left">
            <img src={imageData.url} style={styles.image} alt="" />
          </span>
        </Grid.Column>
      );
    });
  };

imageStorage.js

export async function downloadAllImages(thumbnail) {
  const imageRef = collection(db, "images");
  console.log("hi");
  var images = [];

  const q = query(imageRef, orderBy("date", "desc"), limit(10));
  const querySnapshot = await getDocs(q);

  querySnapshot.forEach(async (doc) => {
    var imagePath = doc.data().ref; // image/task/...
    if (thumbnail) imagePath = imagePath.replace("images", "thumbnails");
    // GET metadata of the image
    const imageRef = ref(storage, imagePath);

    // GET [imagePrefix] URL
    const url = await getDownloadURL(imageRef);
    const fullMetadata = await getMetadata(imageRef);

    images.push({
      path: imagePath,
      url: url,
      metadata: fullMetadata.customMetadata,
    });
  });

  return images;
}

Any way to fix this? Thanks!

CodePudding user response:

Your code is close. Array.prototype.forEach is synchronous though. I suspect your downloadAllImages function isn't waiting for the loop callbacks to complete and returns the images array not populated yet.

Refactor the logic to create an array of Promises (the async callback functions) and await Promise.all them, then you'll have a populated array of images to return.

export async function downloadAllImages(thumbnail) {
  const imageRef = collection(db, "images");

  const q = query(imageRef, orderBy("date", "desc"), limit(10));
  const querySnapshot = await getDocs(q);

  // Map querySnapshot docs to array of async functions (Promises)
  const imageRequests = querySnapshot.docs.map(async (doc) => {
    const imagePath = doc.data().ref; // image/task/...
    if (thumbnail) imagePath = imagePath.replace("images", "thumbnails");
    // GET metadata of the image
    const imageRef = ref(storage, imagePath);

    // GET [imagePrefix] URL
    const url = await getDownloadURL(imageRef);
    const fullMetadata = await getMetadata(imageRef);

    // Return image objects
    return {
      path: imagePath,
      url: url,
      metadata: fullMetadata.customMetadata,
    };
  });

  // Return array of resolved promises (i.e. the image objects)
  return Promise.all(imageRequests);
}
  • Related