I got stuck with the following and haven't found any answer after a lot of research.

What I want to do: simply getting users inluding their images from a firestore-DB with react and the useeffect-hook and displaying them.

The DB-structure looks as follows: https://i.stack.imgur.com/sDcrv.png

So the pictures are a subcollection of the users-collection.

After getting the users from the users-collection, I'm doing a second request for adding the users images to this specific user using Object.assign. After every forEach-run over the users-collection I'm setting the users-array with setUsers((oldUsers) => [...oldUsers, currentUser]);. Logging the users-array shows uses INCLUDING their images.

The problem: When trying to render the images, they are always undefined.

Workaround: Pressing a button that calls a function for re-setting the users:

const reRenderUsers = () => {
    if (userDataLoaded === false) {
      setUserDataLoaded(true);
    }
    const copy = [...users];
    setUsers(copy);
  };

^ This solves the problem and all images where shown.

Question: Is there any possibility showing the images instantly without the need of "re-rendering" the users? Am I using the useEffect-hook wrong for example? I'm thankful for any advice. Many thanks in advance!

Here the full code:

     const [users, setUsers] = useState([]);
     const [userDataLoaded, setUserDataLoaded] = useState(false);

     useEffect(() => {
       const unsubscribe = database.collection("users").onSnapshot((snapshot) => {
         snapshot.forEach((doc) => {
           const currentUser = {
             id: doc.id,
             ...doc.data(),
           };
           database
             .collection("users")
             .doc(currentUser.id)
             .collection("pictures")
             .get()
             .then((response) => {
               const fetchedPictures = [];
               response.forEach((document) => {
                 const fetchedPicture = {
                   id: document.id,
                   ...document.data(),
                 };
                 fetchedPictures.push(fetchedPicture);
               });
   
               currentUser.pictures = [];
               Object.assign(currentUser.pictures, fetchedPictures);
             })
             .catch((error) => {
               console.log(error);
             });
   
           setUsers((oldUsers) => [...oldUsers, currentUser]);
         });
       });
   
       return () => {
         unsubscribe();
       };
     }, []);
   
     const reRenderUsers = () => {
       if (userDataLoaded === false) {
         setUserDataLoaded(true);
       }
       const copy = [...users];
       setUsers(copy);
     };
   
     return (
       <div>
         {!userDataLoaded ? (
           <button onClick={reRenderUsers}> load users </button>
         ) : null}
   
         {users.map((user, index) => (
           <div key={user.id}>
             {user.pictures && <img src={user.pictures[0].imageUrl}></img>}
           </div>
         ))}
       </div>
     );
   }
   
   export default User;
1

There are 1 answers

1
Sameer Kumar Jain On BEST ANSWER

This is because you are calling setUser before the firebase response completes the callback chain. You need to update the state right after the loop inside the success callback completed. I have updated useEffect to update it right after the callback

useEffect(() => {
  const unsubscribe = database.collection("users").onSnapshot((snapshot) => {
    snapshot.forEach((doc) => {
      const currentUser = {
        id: doc.id,
        ...doc.data(),
      };
      database
        .collection("users")
        .doc(currentUser.id)
        .collection("pictures")
        .get()
        .then((response) => {
          const fetchedPictures = [];
          response.forEach((document) => {
            const fetchedPicture = {
              id: document.id,
              ...document.data(),
            };
            fetchedPictures.push(fetchedPicture);
          });
          currentUser.pictures = fetchedPictures;
          setUsers((oldUsers) => [...oldUsers, currentUser]);
        })
        .catch((error) => {
          console.log(error);
        });
      //dont need this here
      //setUsers((oldUsers) => [...oldUsers, currentUser]);
    });
  });

  return () => {
    unsubscribe();
  };
}, []);

Good Luck