1

I'm trying not to write all my code in the same file, and to extract commonly used functions like downloading an image from Firebase to separate functions/files.

I have two functions in React named

  1. OfferCard (renders a card based on props)
  2. getImage (fetches an image's download-URL from Firebase Storage, to provide an image to the OfferCard component).

I'm trying to fetch an image file from getImage and display that image in the returned <CardMedia /> image property from the OfferCard functional component.

The problem I'm facing is, getImage doesn't fetch the data and return it in time for the setOfferImage. Meaning the state variable offerImage remains null or undefined.

In the console, I can see that the getImage is console logging the value AFTER the OfferCard console logs the value/.

I believe it's an issue with asynchronous code and promises but am not too sure how to resolve this one.

OfferCard.js:

import getImage from '../utils/helperFunctions/getImage';

export default function OfferCard(props) {
    const classes = useStyles();
    const [offerImage, setOfferImage] = React.useState(null)

    useEffect(() => {
        if (props.image) {
            initFirebase();
            setOfferImage(getImage(props.image));
            console.log(offerImage)
        }
    }, [])

    return (

        <Link href={`/offers/${props.id}`}>
            <Card className={classes.card} >
                {offerImage ? 
                    <CardMedia
                    image={offerImage}
                /> : <CircularProgress/>
                }
           </Card>
        </Link>
    )
}

getImage:

export default function getImage(path) {
    let storage = firebase.storage();
    let pathReference = storage.ref(`${path}.png`);

    pathReference.getDownloadURL()
        .then((url) => {
            console.log("returning", url);
            return (url);
        })
        .catch((error) => {
            // Handle any errors
            console.log("Could not get image: ", error);
        });
}

Jarrod Watts
  • 345
  • 3
  • 13
  • So is it an issue that the `console.log(offerImage)` is simply logging the *current state* and not the *next state* enqueued from `setOfferImage(getImage(props.image));` a line above, or is it something else entirely? – Drew Reese Aug 31 '20 at 09:10
  • @DrewReese I think you're right that maybe the `console.log` statements were logging old values, I hadn't thought of that. But the issue I was facing was that the conditional ternary operator in the `offerImage? ` section was only ever returning `false`, since offerImage was being returned null by the `getImage` function. – Jarrod Watts Aug 31 '20 at 10:16

3 Answers3

1
useEffect(() => {
  const fetchImg = async () => {
    const res = await getImage(props.image)
    setOfferImage(res);
    // `setOfferImage` is async 
    // `console.log(offerImage)` there will be console previous value of `offerImage`
  }
  if (props.image) {
    initFirebase();
    fetchImg()
  }
}, [props.image])
Liu Lei
  • 1,256
  • 1
  • 9
  • 18
1

the problem in this is that your getImage function is actually a void function, as it will reach the end of the code without finding any return, as the return(url) is performed on a second moment. The solution is to make that function asynchronous, returning a Promise:

export default async function getImage(path) {
  let storage = firebase.storage();
  let pathReference = storage.ref(`${path}.png`);

  return new Promise((resolve, reject) => {
    pathReference.getDownloadURL()
      .then((url) => {
        console.log("returning", url);
        resolve(url);
      })
      .catch((error) => {
        // Handle any errors
        reject(error)
        console.log("Could not get image: ", error);
      });
  })
}

So that you can then await it in your useEffect. Be aware you can't actually await inside an useEffect (learn more), so your OfferCard.js will become

useEffect(() => {
  const getImageAsync = async () => {
    const url = await getImage(props.image)
    if (url)
      setOfferImage(url)
    else {
      // handle error
    }
  }

  if (props.image) {
    initFirebase();
    getImageAsync();
  }
}, [])
Nicola Elia
  • 190
  • 9
  • Thanks Nicola for the quick and correct reply! One small tweak to the code you recommended was to make the `getImage` function async by `export default async function getImage(path)`. Thanks for the help so quickly. – Jarrod Watts Aug 31 '20 at 10:18
0
const [imgUrl, setImageUrl] = useState();

    useEffect(() => {
    console.log('XXXXXXXXXX useEffect');
    if (isSummit) {
        uploadToFireBase();
    }
}, [imgUrl])

.

function start() { uploadFile(file) }

.

async function uploadFile(file) {

    // Create the file metadata
    /** @type {any} */
    const metadata = {
        contentType: 'image/jpeg'
    };

    // Upload file and metadata to the object 'images/mountains.jpg'
    const storageRef = ref(storage, `image/${v4() + file.name}`);
    const uploadTask = uploadBytesResumable(storageRef, file, metadata);

    // Listen for state changes, errors, and completion of the upload.
    uploadTask.on('state_changed',
        (snapshot) => {
            // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
            const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
            console.log('Upload is ' + progress + '% done');
            switch (snapshot.state) {
                case 'paused':
                    console.log('Upload is paused');
                    break;
                case 'running':
                    console.log('Upload is running');
                    break;
            }
        },
        (error) => {
            // A full list of error codes is available at
            // https://firebase.google.com/docs/storage/web/handle-errors
            switch (error.code) {
                case 'storage/unauthorized':
                    // User doesn't have permission to access the object
                    break;
                case 'storage/canceled':
                    // User canceled the upload
                    break;

                // ...

                case 'storage/unknown':
                    // Unknown error occurred, inspect error.serverResponse
                    break;
            }
        },
        async () => {
            // Upload completed successfully, now we can get the download URL
            getDownloadURL(uploadTask.snapshot.ref).then(async (downloadURL) => {
                await setImageUrl(downloadURL);



            });


        }
    );
}

this work for me .

i use async and await to setHook ( await setImageUrl(downloadURL); )

And useEffect to track if my variable imgUrl is changed .

LongPT
  • 1
  • 1