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
OfferCard
(renders a card based on props)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);
});
}
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();
}
}, [])