I am looking to fetch information from a different Firestore collection by looping through data using a forEach method within an onSnapshot function

I'm struggling to grasp the concept of rendering data from Firestore in my project. I've searched extensively but haven't been able to find a solution that fits my requirements.

Background Information

In my Firestore database, I have collections named posts and users. The objective is to display the content stored in these collections within a Posts component on the dashboard.

User Collection Details

The users collection includes an avatar property which contains URLs to user images. Each user's document ID corresponds to their unique username.

Posts Collection Specifics

Within the posts collection, there's an author field that aligns with the username/doc.id value for each post.

Main Objective

When iterating through the posts, I aim to create an array that stores the id and other relevant data related to each post. Additionally, it's crucial to establish a connection with the users collection so that, during each iteration, the script can fetch the corresponding avatar based on the post author.

Prior Attempts Made

One approach I experimented with involved utilizing async/await alongside the forEach loop. This method attempts to retrieve the correct user document by using the post.author value and extracting the associated avatar.

Posts Component Script:

import { useEffect, useState } from "react"
import { Link } from "react-router-dom"
import { collection, onSnapshot /*doc, getDoc*/ } from "firebase/firestore"
import { db } from "lib/firebase"
import AllPostsSkeleton from "components/Skeletons/AllPostsSkeleton"
import Tags from "components/Tags"
import defaultAvatar from "assets/images/avatar_placeholder.png"

// Further code implementation continues...

Answer №1

When dealing with Promises, particularly in the context of the Firebase SDK, it is common to utilize

Promise.all(docs.map((doc) => Promise<Result>))
to ensure that the Promises are properly linked together.

However, chaining Promises correctly can lead to a situation where multiple sets of data might clash if a snapshot arrives while processing the previous set of documents. To address this issue, it is crucial to "cancel" the Promise chain triggered by the previous execution of the listener each time the snapshot listener fires again.

function getAvatarsForEachPost(postDocSnaps) {
  return Promise.all(
    postDocSnaps.map((postDocSnap) => {
      const postData = postDocSnap.data();

      const userDocRef = doc(db, "users", postData.author)
      const userDocSnap = await getDoc(userDocRef)

      return {
        id: postDocSnap.id,
        avatar: userDocSnap.get("avatar"),
        ...postData,
      };
    })
  )
}

useEffect(() => {
  let cancelPreviousPromiseChain = undefined;
  const unsubscribe = onSnapshot(
    collection(db, "posts"),
    (querySnapshot) => { 
      if (cancelPreviousPromiseChain) cancelPreviousPromiseChain(); 

      let cancelled = false;
      cancelPreviousPromiseChain = () => cancelled = true;

      getAvatarsForEachPost(querySnapshot.docs)
        .then((postsArray) => {
          if (cancelled) return; 
          setLoading(false)
          setPosts(postsArray)
        })
        .catch((error) => {
          if (cancelled) return; 
          setLoading(false)
          console.log(error)
        })
    },
    (error) => {
      if (cancelPreviousChain) cancelPreviousChain(); 
      setLoading(false)
      console.log(error)
    }
  )

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

Some additional notes:

  • setLoading(false) should be invoked only after data has been fetched, not immediately upon attaching the listener as mentioned in the original answer.
  • Consider utilizing setError() or similar functions to display error messages for users in case of any issues.
  • You may simplify the code by letting a child component like <Post> handle fetching the avatar URL.
  • It's worth considering creating a getUserAvatar(uid) function with an internal cached map of
    uid‑>Promise<AvatarURL>
    entries to minimize redundant requests to the database for the same information.

Answer №2

Your current problem stems from the integration of an await statement within a forEach loop. Due to its nature, forEach does not return anything, making it incompatible with await. Consider switching to using Map instead, as it will allow your logic to proceed smoothly.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Controlling numerous websockets using React

I am currently developing a single-page application using React.js with a JSON RPC 2.0 backend API that relies on websockets. Managing multiple websocket connections simultaneously across different React.js components has been a challenge. Initially, I th ...

Navigating to the parent Vue component in a Vue3 project with Composition API structure in WebStorm

After transitioning to Vue3 and embracing the Composition API style, I find myself missing a small convenience that I had when developing in the previous Options API pattern. In WebStorm/IntelliJ IDE, I used to be able to command-click (Mac) on the "export ...

There seems to be a malfunction with the hide and reset features, as they are

I have encountered an issue while working on hiding a series in Google Charts when clicked on the legend. The problem arises when an extra column is added to display tooltips on the bars, causing the functionality to break. Please check out the demos below ...

Display geographic data using d3 with geoJSON

I am struggling to render a geoJSON file using d3 because I am having trouble targeting the correct features for projection. Instead of working with the typical us.json file used in many d3 examples, my current map focuses on United States "Commuting Zone ...

Having trouble with the CSS positioning of divs created with JavaScript: it's not behaving as anticipated

Let me start by saying I have always struggled with CSS positioning. It seems like I am missing something simple here... So, I have a JS script that generates divs within a parent container called #container which is set to absolute position. Here is the ...

Utilizing jQuery Ajax to submit multiple forms using a single selector in one go

I'm having an issue with jQuery Ajax involving multiple forms. Each time I execute it, only the top form works properly while the others do not function as expected. Can anyone provide assistance with this? Here is the source code: <form id="form_ ...

The font remains the same despite the <Div style=> tag

I have a script that loads external HTML files, but I am facing an issue with changing the font to Arial. The script is as follows: <script type="text/javascript"> $(document).ready(function(){ $("#page1").click(function(){ ...

Dispatching information to a designated Google Analytics tracking code

Within our website, we have a unique dimension that is configured on Google Analytics and utilized when sending the page view event: gtag('config', 'UA-TrackingCode', { 'custom_map': { 'dimension1': &apo ...

Tips on integrating the createjs library into a ReactJS project

Hey there! I'm currently working on developing a canvas-based app using ReactJS, and I need to integrate the CreateJS library. As a newcomer to ReactJS, I've been struggling to figure out the best approach. I've tried two different methods - ...

Every time I switch views using the router in vue.js, my three.js canvas gets replicated

After creating a Vue.js integrated with three.js application, I encountered an issue with the canvas getting duplicated every time I opened the view containing the three.js application. The canvas remained visible below the new view, as shown in this image ...

Creating numerous strings using template literals

I am looking to create multiple strings using a template literal and an array variable. For instance, a template literal allows you to replace an expression with its content in a string: var = "world"; tpl = `Hello ${var}!`; console.log(tpl); // Hello wor ...

Typescript-powered React component for controlling flow in applications

Utilizing a Control flow component in React allows for rendering based on conditions: The component will display its children if the condition evaluates to true, If the condition is false, it will render null or a specified fallback element. Description ...

Unforeseen behavior in the definition of requirejs

This first snippet of code is functional: define([ 'jquery', 'underscore', 'backbone' ], function($, _, Backbone,home_template) { var HomeView = Backbone.View.extend({ render: function() { ...

In TypeScript, the 'onChange' is declared multiple times, therefore this particular usage will be scrutinized carefully

Within my React project, I am utilizing material-ui, react-hook-form, and Typescript. However, I encountered an error in VSCode when attempting to add the onChange function to a TextField component: 'onChange' is specified more than once, resul ...

Issue with printing data consecutively using Javascript

My JavaScript code aims to display the content of the body tag again when the add button is clicked. The purpose is to add authors, with each author being added in sequence. For example, if there is only 1 author present, the user simply selects whether th ...

Async/Await moves on to the next function without waiting for the previous function to finish executing

I am developing a web application that requires querying my database multiple times. Each query depends on the data retrieved from the previous one, so I need to ensure each call completes before moving on to the next. I have attempted using async/await fo ...

Tips for validating CORS response header values when using MongoDB in NextJS

Currently, I'm working on a NextJS page that utilizes getServerSideProps to fetch data from MongoDB. One of the fields retrieved is a URL to an image which I am successfully displaying. However, my challenge lies in obtaining the dominant color of th ...

Presenting a trio of distinct tables each accompanied by its own unique button option

I am attempting to create a functionality where there are 3 buttons and when a user clicks on one of them, it shows the corresponding table while hiding the other two. I have experimented with using getElementById to manipulate the display property of the ...

The ng-app directive for the Angular project was exclusively located in the vendor.bundle.js.map file

Currently, I am diving into an Angular project that has been assigned to me. To get started, I use the command "gulp serve" and then access the development server through Chrome by clicking on "http://localhost:3000". During my investigation in Visual Stu ...

Leveraging the csv file functionality in the npm package of d3 version 5

Currently in my react project, I am utilizing d3 for data visualization. After installing the d3 module via npm, I found myself with version 5.9.2 of d3. The challenge arose when attempting to use a csv file within d3. Despite scouring various resources, I ...