Why array is empty on render but if code is changed its fills

40 Views Asked by At

So I am fetching the urls from a firestore database and then using those urls for the img tags.

However, the issue I am facing is that when the page loads the array is empty but if I make a change to the code ( any change at all even an empty space ) the array will then populate as the useeffect triggers again.. I would like for it to get the data on the first fetch.

First line of the console log is when the page loads, then the second line is when I added a random space to a div

enter image description here

import React, { useEffect, useState } from "react"
import { proFirestore } from "../firebase/config"
import { collection, onSnapshot, query } from "firebase/firestore"

const Photos = () => {
  const [imgs, setImgs] = useState([])

  const fetchAllImgs = async () => {
    try {
      const collectionRef = collection(proFirestore, "images")
      const qry = query(collectionRef)

      await onSnapshot(qry, (querySnapshot) => {
        setImgs(
          querySnapshot.docs.map((doc) => ({
            id: doc.id,
            data: doc.data(),
          }))
        )
      })
    } catch (error) {}
  }

  useEffect(() => {
    fetchAllImgs()
    console.log(imgs)
  }, [])

  return (
    <div className="absolute mt-[5%] flex w-full items-center justify-center">
      <div className="">
        <div>
          <button className="h-20 w-20   rounded bg-black text-white"></button>
        </div>
        <div>
          <img className="h-full w-full cursor-pointer" src={imgs[0]}></img>
        </div>
      </div>
    </div>
  )
}
export default Photos

I have tried many things such as .map() which works, but I want to only render a single item from the array based on its index..

Any help would be great?

3

There are 3 best solutions below

1
Brian On BEST ANSWER

When loading the page, its best to use a ? to check the array before populating the div with the data, this works perfectly

{imgs[1]?.data.imageUrl}
0
Skar On

If you change it to this, does it work?

const fetchAllImgs = async () => {
  try {
    const collectionRef = collection(proFirestore, "images");
    const qry = query(collectionRef);

    const querySnapshot = await getDocs(qry);
    const imgData = querySnapshot.docs.map((doc) => ({
      id: doc.id,
      data: doc.data(),
    }));

    setImgs(imgData);
  } catch (error) {

    console.error("Error fetching images:", error);
  }
};

or this:

const fetchAllImgs = () => {
  const collectionRef = collection(proFirestore, "images");
  const qry = query(collectionRef);

  getDocs(qry)
    .then((querySnapshot) => {
      const imgData = querySnapshot.docs.map((doc) => ({
        id: doc.id,
        data: doc.data(),
      }));
      setImgs(imgData);
    })
    .catch((error) => {
      console.error("Error fetching images:", error);
    });
};

4
Quentin On

Each time the component renders this line:

const [imgs, setImgs] = useState([])

… reads the value out of the state and assigns it to imgs which is a constant.


This chunk of code:

useEffect(() => {
  fetchAllImgs()
  console.log(imgs)
}, [])

will, the first time (because the dependencies array is empty) the component renders will call fetchAllImgs and then log the value of imgs.

fetchAllImgs calls setImgs which stores a new value in the state but it doesn't change the value of imgs.

Remember, imgs is a constant that is assigned a value at the top of the function rendering the component.


Move the console.log outside of the effect.


Also examine the log output of this example which keeps a count of renders:

const App = () => {
  const [count, setCount] = useState(1);
  const [array, setArray] = useState([]);

  useEffect(() => {
    setCount(count => count + 1);
    setArray(['a', 'b', 'c']);
  }, [])

  console.log({count, array});

  return (
    <div>
      <p>Render count: {count}</p>
    </div>
  );
};