Array.map started to error after adding an asynchronous call inside

66 Views Asked by At

I have a React component that maps through an array of messages and returns a bunch of JSX based on a few conditions:

messages.map(async (msg) => {
  let previewImage: URL | undefined = undefined;

  if (msg.mediaId) {
    previewImage = await storage.getFilePreview(
      Constants.BUCKET_ID,
      msg.mediaId
    );
  }

  if (...) {
    // System messages
    return JSX;
  }

  if (...) {
    // My messages
    return JSX;
  }

  if (...) {
    // Other person's messages
    return JSX;
  }
});

This used to work fine before I added the async function to get the preview image if the message contains a mediaId. However, after making the function async because I needed to await the getFilePreview call, I'm now getting this error:

Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

2

There are 2 best solutions below

1
JAGC On BEST ANSWER

First take into account the comment left by @ray as it explains everything. With this in mind, I recommend you create a component that will take care of rendering a message, so you can leave the responsibility of loading the preview of your image to it without affecting your messages.map(...) and your code will be more readable.

For example, look at this snippet:

function Message({ message }) {
  const [previewImage, setPreviewImage] = useState(undefined);

  useEffect(() => {
      let ignore = false;

      setPreviewImage(undefined);
      storage
        .getFilePreview(Constants.BUCKET_ID, msg.mediaId)
        .then((preview) => {
            if(!ignore) {
              setPreviewImage(preview)
            }
          });

      return () => {
        ignore = true;
      }
    },
    [message.mediaId]
  );

  if (...) {
    // System messages
    return JSX;
  }

  if (...) {
    // My messages
    return JSX;
  }

  if (...) {
    // Other person's messages
    return JSX;
  }
}

Then your initial code would look like this:

messages.map((msg) => <Message message={msg} key={msg.id} />);

You can learn more about why I suggested getting the preview asynchronously using useEfffect here.

1
Alejandro Tamayo On

The error you're encountering is because you're trying to return a Promise ([object Promise]) from within the map function, but React expects synchronous values to be returned in order to render JSX.

In your code, you're using async within the map callback function, which means that some of your return values might be Promises. React doesn't know how to handle Promises directly as JSX content. To fix this, you need to structure your code so that you're only returning synchronous JSX elements.

Here's how you can achieve this:

// Assuming this is inside a React component's render or JSX-returning function

// Use Promise.all to await all the necessary async operations
const mappedJSX = await Promise.all(messages.map(async (msg) => {
  let previewImage: URL | undefined = undefined;

  if (msg.mediaId) {
    previewImage = await storage.getFilePreview(
      Constants.BUCKET_ID,
      msg.mediaId
    );
  }

  if (...) {
    // System messages
    return systemJSX; // Replace with actual JSX for system messages
  }

  if (...) {
    // My messages
    return myJSX; // Replace with actual JSX for your messages
  }

  if (...) {
    // Other person's messages
    return otherPersonJSX; // Replace with actual JSX for other person's messages
  }

  return null; // Default case, or you can handle this differently
}));

// Now you can render the mapped JSX
return <div>{mappedJSX}</div>;