Reputation: 3
so I am trying to fetch images from Firebase storage and then display them in a React component. Frankly, I'm new to React/Javascript and I'm having a hard time understanding the asynchronous nature of React/JS and I'm running into the problem where my images are fetched but the component is not re-rendered upon completion of the fetch...
This is the code on my main page, in the useEffect function, I am trying to fetch the images and then store their download urls in an array and then set that array in state (upon page load i.e. only once). Since these are promises, its not happening synchronously and when I first load the page it displays no images. If I, however, click on a different component on the page, it re-renders my Pictures component and the images show up(??), so I know the fetch has worked.
let storageRef = firebase.storage().ref()
let calendarRef = React.createRef()
const position = props.location.state.name.indexOf("@")
const username = props.location.state.name.substring(0, position);
const [simpleDate, setSimpleDate] = useState(null)
const [selectedDate, setSelectedDate] = useState('')
const [showModal, setShowModal] = useState(false)
const [showCancelModal, setShowCancelModal] = useState(false)
const [expectedPeople, setExpectedPeople] = useState(null)
const [events, setEvents] = useState([])
const [helpModal, showHelpModal] = useState(false)
const [pictureURLs, setPictureURLs] = useState([])
useEffect(() => {
//load pictures
const fetchImages = async () => {
let urls = []
storageRef.child('cabinPictures').listAll().then((result) => {
result.items.forEach((imageRef) => {
imageRef.getDownloadURL().then(url => {
urls.push(url)
})
})
})
return urls;
}
fetchImages().then(urls => {
setPictureURLs(urls);
console.log("inside .then() " + pictureURLs)
})
//fetch reservations
firebase
.firestore()
.collection('reservations')
.onSnapshot(serverUpdate => {
const reservations = serverUpdate.docs.map(_doc => {
const data = _doc.data();
data['id'] = _doc.id;
return data;
});
let fetchedEvents = reservations.map(reservation => {
let date = reservation.reservationDate.toDate()
const month = ("0" + (date.getUTCMonth() + 1))
let dateString = date.getUTCFullYear() + "-" + ("0" + (date.getUTCMonth()+1)).slice(-2) + "-" + ("0" + date.getUTCDate()).slice(-2)
return {title: reservation.username + " - " + reservation.numPeople + " total", date: dateString, id: reservation.id, totalPeople: reservation.numPeople, month: month}
})
console.log(fetchedEvents)
setEvents(fetchedEvents)
});
}, [])
My Pictures component in the main page where the useEffect (above) is run. I pass the urls from state as a prop:
<div className="pictures-div-container">
<Pictures pictureURLs={pictureURLs}>
</Pictures>
</div>
The code for my Picture component:
import React, { useState, useEffect } from 'react'
import styles from "./styles.css"
const firebase = require('firebase');
const Pictures = (props) => {
const [uploadImage, setUploadImage] = useState(null)
const [progressValue, setProgressValue] = useState(0)
let storageRef = firebase.storage().ref()
let { pictureURLs } = props
const handleUpload = () => {
setProgressValue(0)
const uploadTask = storageRef.child(`cabinPictures/${uploadImage.name}`).put(uploadImage)
uploadTask.on('state_changed',
(snapshot) => {
//progress function
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes ) * 100)
setProgressValue(progress)
},
(error) => {
//error function
console.log(error)
},
() => {
//complete function
storageRef.child('cabinPictures').child(uploadImage.name).getDownloadURL().then(url => {
console.log(url)
} )
});
}
const handleFileSelect = (e) => {
if (e.target.files[0]) {
setUploadImage(e.target.files[0])
}
}
return (
<div className="pictures-container">
<h2>Upload a Picture!</h2>
<button className="upload-button" onClick={() => handleUpload()}>Upload</button>
<input type="file" onChange={(e) => handleFileSelect(e)}></input>
<progress value={progressValue} max="100"></progress>
<div className="pictures">
{
pictureURLs.map((url, index) => {
return <img className="picture" key={index} src={url}></img>
})
}
</div>
</div>
)
}
export default Pictures
So, can anyone help me understand why the Pictures component is not re-rendering automatically when the state is set after fetching the picture urls from firebase? I thought that when a prop changes in a component, the whole component is re-rendered?
EDIT: So this is what I changed in my main page's useEffect function as per the answer's suggestions, and it works!
//fetch and load pictures
const fetchImages = async () => {
let result = await storageRef.child('cabinPictures').listAll();
let urlPromises = result.items.map(imageRef => imageRef.getDownloadURL())
return Promise.all(urlPromises)
}
const loadImages = async () => {
const urls = await fetchImages()
setPictureURLs(urls)
}
loadImages()
Upvotes: 0
Views: 2388
Reputation: 171679
You have to let all the nested promises resolve before you return urls
I am not very current on firebase API so am not sure if result.items
is an actual array or an object that has a foreach method. Following should work if it is a js array
Try something like:
//load pictures
const fetchImages = async() => {
let result = await storageRef.child('cabinPictures').listAll();
/// map() array of the imageRef.getDownloadURL() promises
let urlPromises = result.items.map(imageRef => imageRef.getDownloadURL());
// return all resolved promises
return Promise.all(urlPromises);
}
const urls = await fetchImages()
setPictureURLs(urls);
console.log("inside .then() ", urls)
Upvotes: 1