1

Here is what I have tryed to create.

const getPostImgSrc = async (postImg) => {
    const imgRef = ref(storage, `postsImgs/${postImg}`);

    getDownloadURL(imgRef).then((url) => {
       return <img src={url} alt="" />
    });
};

However, the problem is that when I searched the web, it seems like the url is unable to render in time since in async it's still processing which in turn returns me an img with a src=[object promise]. However when I remove the async it dosen't work since it needs that async otherwised it would be an error.

So I'm wondering if there is a way to get the url from the getDownloadURL() with or without async? Here is what I'm trying to do with the function, for each post someone has created with an image, get that img url and pass it through in which returns an img element with that src.

<div className="post-details">
      <p>{post.text}</p>

      {post.img === null ? (
          <></>
          ) : (
            getPostImgSrc(post.img)
      )}
</div>

Thanks in advance :)

KTK
  • 35
  • 6
  • what is that ref function? I know about useRef but not ref(), if that is an async function you will need to await it, otherwise you will always receive that promise object. – GhostOrder Mar 13 '23 at 21:47
  • @GhostOrder the ref function is from firebase storage and it Returns a StorageReference for the given url. – KTK Mar 13 '23 at 21:56
  • Oh I see, can we see the code for `getDownloadURL` that `url` should contain a value and not a promise because the `then` block is called precisely when a promise is resolved so maybe the issue lies in that function. – GhostOrder Mar 13 '23 at 22:08
  • @GhostOrder The getDownloadURL is also from firebase storage but from what I can find is `(alias) function getDownloadURL(ref: StorageReference): Promise import getDownloadURL` – KTK Mar 13 '23 at 22:24
  • gotcha, can you try the async/await way of calling async functions? that is instead of `getDownloadURL(imgRef).then ...` replace the whole call with this `const res = await getDownloadURL(imgRef)` and then log what is in `res`, the url should be there – GhostOrder Mar 13 '23 at 22:30
  • @GhostOrder yes, it gives me an url of the picture file and when I click on it, it shows me the picture so that's nice! However before that, it showed me 4 errors which says `Uncaught 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.` – KTK Mar 13 '23 at 22:41
  • Glad to know it worked, the new error message is easier to get rid of, let me write the complete answer. – GhostOrder Mar 13 '23 at 22:54
  • You need to: 1) start loading the image URL when the component is created, 2) store the image URL in the state, 3) render the image URL from the state. In short: https://stackoverflow.com/a/64645604/209103 and (not so short) https://stackoverflow.com/questions/70521565/getdownloadurl-takes-some-time/70521871#70521871 – Frank van Puffelen Mar 14 '23 at 00:22

2 Answers2

1

I think what happens is that you a re mixing two ways of handling Promises, the async/await way and the then/catch way. If you are using then/catch then you don't need to be inside an async function, on the other side, if you are inside an async function then you should be using the await keyword when handling promises and not the then method.

So instead of:

getDownloadURL(imgRef).then((url) => {
  return <img src={url} alt="" />
});

Let's use await since you already are using an async function:

const getPostImgSrc = async (postImg) => {
  const imgRef = ref(storage, `postsImgs/${postImg}`);    
  const res = await getDownloadURL(imgRef);
};

This function now is getting the image url properly, but we have a little issue now, because it is an async function, and all async functions always return a Promise, our getPostImgSrc is returning Promise<undefined>, no matter what you return from an async function it will always be wrapped inside a Promise (in this case the value is undefined beacuse we are not even explicitly returning a value) and as you may know Promises are just objects, so this is why you are getting:

Uncaught 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.

Because here:

<div className="post-details">
      <p>{post.text}</p>

      {post.img === null ? (
          <></>
          ) : (
            getPostImgSrc(post.img) // <---- here
      )}
</div>

You are trying to render the value that getPostImgSrc returns (which will always be a Promise since this is an async function, which is an object).

To fix this we first need to return explicity the image url we are getting in getPostImgSrc:

const getPostImgSrc = async (postImg) => {
  //... firebase code here 
  const res = await getDownloadURL(imgRef);
  return res;
};

Then we need a place to store that value, we will use useState for that.

Then we need to call getPostImgSrc from another place in our component. Because I don't know how your component code is structured I will call it as soon as the component is rendered, that is using useEffect without dependencies.

import { useState, useEffect } from 'react';

// inside the component
const MyComponent = () => {
  const [ imageUrl, setImageUrl ] = useState()

  // other component code lines here

  useEffect(() => {
    async function callFunction () {
       const url = await getPostImgSrc(post.img)
       setImageUrl(url)
    }
    callFunction();
  }, [])

}

Finally in the JSX part we put the img tag directly:

<div className="post-details">
      <p>{post.text}</p>

      {post.img === null ? (
          <></>
          ) : (
            <img src={imageUrl} alt="" /> // <---- here
      )}
</div>
GhostOrder
  • 586
  • 7
  • 21
  • what happens if I can't do `await getPostImgSrc(post.img)` ? My component is kind of too large to paste it here but basically there is a container where there is the post name, time created, image, and text. So basically I'm learning to create like a social media type of thing where people can post messages. The post code is in `{postList.map((post) => ( >POST CODE HERE< ))}` – KTK Mar 14 '23 at 00:00
  • also when I tried the `await getPostImgSrc(post.img)` I got an error saying `Parsing error: Unexpected reserved word 'await'.` – KTK Mar 14 '23 at 00:01
  • I've just edited the answer to handle that case, basically instead of calling `await getPostImgSrc(post.img)` directly in the JSX, you just render the img tag ``, that `imageUrl` value comes from a state prop create using `useState`, more in the answer. – GhostOrder Mar 14 '23 at 00:23
  • Um.. I have a question about the callFunction(). In the `await getPostImgSrc(postImg)` where did you get the `postImg`? Because in my original code where your `` was, the code was `getPostImgSrc(post.img)` because I needed the post.img which came from `{postList.map((post) => ( >IMG CODE HERE< ))}` since the post has data in it so that post has my name, text, and etc including the img URL ID. – KTK Mar 14 '23 at 00:33
  • @KTK my mistake, that should be `post.img` as in your original code – GhostOrder Mar 14 '23 at 00:38
0

The problem with your code is that getDownloadURL is an asynchronous function that returns a Promise. You need to use async/await or .then() to handle the result of the Promise.

const getPostImgSrc = async (postImg) => {
    const imgRef = ref(storage, `postsImgs/${postImg}`);
    const url = await getDownloadURL(imgRef);
    
    return <img src={url} alt="" />;
};

Then in your post-details component, you can call the getPostImgSrc function to render the image.

<div className="post-details">
    <p>{post.text}</p>

    {post.img === null ? (
        <></>
    ) : (
        await getPostImgSrc(post.img)
    )}
</div>
Davis Jahn
  • 441
  • 5
  • 17