Search code examples
reactjsjsx

How to Render the JSX Returned by an Async Function Called From Inside Array.Map() in React?


In my React application, I am calling an async function that renders a JSX from inside array.map(), but nothing is being rendered. I confirmed that the function is being called properly.

I suspect that there is something related to the rendering being executed before the async function returns its result, but I do not understand why this would happen if the async function is using await in its internal calls

<div>
   {
      gallery.map((image) => 
         {renderImage(image, 'vegan-mundi-gallery')}
   )}
</div>

I tried wrapping the function that generates the JSX inside a React Fragment (<></>) as in the snippet below, but then I get the error "Objects are not valid as a React child (found: [object Promise])".

{
   gallery.map((image) => (
   // The react fragment below is generating the error Objects are not valid as a React child    (found: [object Promise]) - which reinforced the idea that the async function haven’t returned its result by the time of the rendering
      <>
         {renderImage(image, 'vegan-mundi-gallery')}
      </>
   )
)}

Does anyone can help with this?

This is my complete code:

import { useEffect, useState } from "react";

export default function Gallery (){

    const [isLoading, setIsLoading] = useState(true);
    const [gallery, setGallery] = useState([]);

    useEffect( ()=> {
    // some code that gets the gallery array from an API call and handles the isLoading state 
    } , []);

    const renderImage = async(item, bucket) => {

        const photo = item.PHOTO;
        const data = await fetch(`http://1.1.1.1:4000/s3/${bucket}/${photo}`);
        const preSignedUrl = await data.json();
        console.log(preSignedUrl) // I can see the preSignedUrl in the console, so the async call worked!

        return (
            <figure>
                <div>
                    <img key={photo}
                        src={`${preSignedUrl}`}
                    />
                </div>
                <figcaption>{item.LABEL}</figcaption>
            </figure>
        )
    }


    if (isLoading){
        return(<p>loading...</p>);
    }
    else {

        return (
                <div>
                    {
                        gallery.map((image) => 
                                {renderImage(image, 'vegan-mundi-gallery')}

                    )}
                </div>
          )
    }
}

I checked ChatGPT, and it suggested exactly what I have done with the <></>.


Solution

  • Better use useEffects to handle this situation.

    const [gallery, setGallery] = useState([]);
    
    useEffect(() => {
            // Simulate API call to get gallery array
            const fetchGallery = async () => {
                const galleryData = [
                    { PHOTO: 'photo1.jpg', LABEL: 'Label 1' },
                    { PHOTO: 'photo2.jpg', LABEL: 'Label 2' },
                ];
                setGallery(galleryData);
            };
    
            fetchGallery();
        }, []);
    

    for fetching images

    const [images, setImages] = useState([]);
    const [isLoading, setIsLoading] = useState(true);
    
    
    useEffect(() => {
            const fetchImages = async () => {
                const fetchedImages = await Promise.all(
                    gallery.map(async (item) => {
                        const photo = item.PHOTO;
                        const response = await fetch(`http://1.1.1.1:4000/s3/bucketname/${photo}`);
                        const preSignedUrl = await response.json();
                        return { ...item, url: preSignedUrl };
                    })
                );
                setImages(fetchedImages);
                setIsLoading(false);
            };
    
            if (gallery.length > 0) {
                fetchImages();
            }
        }, [gallery]);
    
    if (isLoading) {
            return (<p>loading...</p>);
        } else {
            return (
                <div>
                    {images.map((image) => (
                        <figure key={image.PHOTO}>
                            <div>
                                <img src={image.url} alt={image.LABEL} />
                            </div>
                            <figcaption>{image.LABEL}</figcaption>
                        </figure>
                    ))}
                </div>
            );
        }