Josh Simon
Josh Simon

Reputation: 259

Firebase call inside useEffect is not returning data properly

I have a component Photo.js responsible for making a call to to my firestore and rendering the returned data. The returned data is set to a state variable venues.
This data is then mapped over and rendered to the browser, however I'm getting the following error in the browser:

Cannot read properties of null (reading 'map')

And when I console log the state variable venues, it's being returned as null.
If I comment out the code responsible for mapping out the returned data (below), my webpage renders without problem - and if I uncomment the same code and save, the firebase call works and the data is rendered:

            {venues.map((item) => {
                return(<img src = {item.photoUrl}/>)
            })}

Here's the Photos component controlling the firebase call:

import { useState,useEffect } from 'react'
import {getVenues} from '../../services/firebase.js'

const Photo = () => {
    const [ venues,setVenues ] = useState(null)

    useEffect(() => {
        console.log('it got here')
        async function getAllVenues(){
            const response = await getVenues()
            await setVenues(response)
        }
        getAllVenues()
    },[])

    console.log(venues)
    return(
        <div className = 'venueCard-container'>
            {venues.map((item) => {
                return(<img src = {item.photoUrl}/>)
            })}
        </div>
    )
}

export default Photo

...and the the firebase functions in services/firebase.jss

import {firebase} from '../firebaseConfig'

export async function getVenues() {
    const response = await firebase
    .firestore()
    .collection('venues')
    .get()
    return response.docs
        .map((venue) => ({...venue.data()}))
}

I'm thinking this is some sort of async problem - the component is rendering before the firebase call has returned the data. Suggestions?

Upvotes: 1

Views: 283

Answers (1)

Nicholas Tower
Nicholas Tower

Reputation: 84912

const [ venues,setVenues ] = useState(null)

You've set the initial value of the state to be null, so that's what it will be on the first render. Some time later the data will finish loading and you'll render again, but until that time, your component needs to work with the initial state. You could check for null and render nothing:

return(
    <div className = 'venueCard-container'>
        {venues && venues.map((item) => {
            return(<img src = {item.photoUrl}/>)
        })}
    </div>
)

...or you could render a placeholder:

if (!venues) {
    return <div>Loading...</div>
} else {
    return (
        <div className = 'venueCard-container'>
            {venues.map((item) => {
                return(<img src = {item.photoUrl}/>)
            })}
        </div>
    )
  );
}

...or you could make the initial state be an empty array, which means it will always have a .map method even before loading has finished:

const [ venues,setVenues ] = useState([])

Upvotes: 1

Related Questions