Jake
Jake

Reputation: 3

Fetching and displaying images from firebase on React.js app

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

Answers (1)

charlietfl
charlietfl

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

Related Questions