Skip to content

Use firebase onSnapshot() in for loop?

The below code works for getting my data from firestore. I’m trying to update this to use onSnapshot() instead of of get(). Maybe the core of my confusion is onSnapshot() doesn’t return a promise and I’ve tried just adding the listeners into an array but it seems the data doesn’t get updated. How do I iterate over a for loop of onSnapshot()‘s and render the results?

const [activityDataArray, setActivityDataArray] = useState([]);
const userActivityIds = userData.activities

useEffect(() => {
    let promises = [];
    for (const activityId of userActivityIds) {
        promises.push(getFirestoreData("activities", activityId));
    }
    Promise.all(promises).then(response => setActivityDataArray(response));
}, [userActivityIds]);

UPDATED CODE: When I console.log() the array it has my data, but I think this is a trick with chrome dev tools showing new information. I think when I call setActivityDataArray it’s running it on an empty array and then it never gets called again. So the data doesn’t render unless I switch to a different tab in my application and go back. Then it renders correctly (so I know the data is good, it’s just a rendering issue). I think I need to re-render within onSnapshot() but how do I do this correctly?

const [activityDataArray, setActivityDataArray] = useState<any>([]);
const userActivityIds: string[] = userData.activities

useEffect(() => {
    let activityDataArrayDummy: any[] = []
    for (const i in userActivityIds) {
        firebase.firestore().collection("activities").doc(userActivityIds[i])
            .onSnapshot((doc) => {
                activityDataArrayDummy[i] = doc.data();
            });
    }
    console.log("activityDataArrayDummy", activityDataArrayDummy)
    setActivityDataArray(activityDataArrayDummy);
}, [userActivityIds]);

Answer

Simply calling onSnapshot() in a loop should do it.

import { doc, onSnapshot } from "firebase/firestore";

for (const activityId of userActivityIds) {
  // get reference to document
  const docRef = doc(db, "activities", activityId)
  
  onSnapshot(docRef, (snapshot) => {
    // read and render data from snapshot
  })
}

However, if you ever need to unsubscribe from any of the listeners then you might have to store the Unsubscribe function returned by onSnapshot somewhere in state.


Just in case you have 10 or less items in userActivityIds then you can use onSnapshot() with a Query instead:

const q = query(collection(db, "activities"), where(documentId(), "in", userActivityIds));

onSnapshot(q, (querySnapshot) => {
  // ... 
})