'Fetching data after forEach() from Firestore in Javascript
Im building a workout web app and need to show how many round are going to do done per workout and how many exercises per round.
As the amount of rounds and exercise per round will be different for every workout, I've set them as forEach.
But I need to know the docID (shown as "roundID") of the rounds collection to get that specific collections, sub collection to show the specific exercises.
Currently it is not showing the exercises as I need to wait for the first fetch to be complete and then use that docId which, I am unable to do.
I have tried await with loops, async but have been unable to get it to work.
Currently my code is:
Main forEach getting the rounds
const Rnds = document.getElementById("rnds");
const roundRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds"));
const unsub = onSnapshot(roundRef, (querySnapshot) => {
querySnapshot.forEach((doc) => {
const roundID = doc.id;
const rounds = doc.data().round;
const rests = doc.data().rest;
console.log("Round", roundID);
Rnds.innerHTML += `
<h2>${rounds}</h2>
A list of the amount of exercise docs in "exercise" collection
<p>${rests}</p>
`
});
});
and then, fetch the exercise for the rounds
const exerciseRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds", roundID, "exercises"));
const exerciseUnsub = onSnapshot(exerciseRef, (querySnapshot) => {
querySnapshot.forEach((doc) => {
const reps = doc.data().rep;
const names = doc.data().exerciseName;
console.log("Exercises", doc.data());
});
});
Question Update
Currently I can fetch the rounds and the correct exercise docs that go with them that show in the console. Ive managed to get the rounds to display but unable to to pass in the names of the exercises to innerHTML.
// Create Exercise Rounds
const Rnds = document.getElementById("rnds");
const roundRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds"));
const roundsUnsub = onSnapshot(roundRef, (querySnapshot) => {
querySnapshot.forEach((doc) => {
const DocId = doc.id;
const rounds = doc.data().roundName;
const rests = "Rest: " + doc.data().rest;
// Calls the `runQuery` async function to get the exercises documents.
runQuery(DocId).then((result) => {
// Initialized an object
const obj = {};
const names = obj.exerciseNames;
// Assigns the exercises' documents data to its round collection's documents' Id.
obj[DocId] = result;
// Logs the constructed object.
console.log(obj);
// Put your `innerhtml` here.
Rnds.innerHTML += `
<h2>${rounds}</h2>
<p>${names}</p>. // <- unable to get this to show
<p>${rests}</p>
`
});
});
});
async function runQuery(documentId) {
const exerciseRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds", documentId, "exercises"));
const querySnapshot = await getDocs(exerciseRef);
if (!querySnapshot) {
return null;
}
// Returns a map of data (array).
return querySnapshot.docs.map((doc) => doc.data());
}
roundsUnsub;
Solution 1:[1]
There's a way to associate or get the data of exercises collection but there are some points I want to point out:
onSnapshotonly listens to the changes of all the documents which you query on this line of code (which is the documents from the rounds collection):const roundRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds"));- If there are changes from the exercises documents then you'll not be able to get realtime updates from it.
To get realtime updates from the rounds collection and associate the exercises to the rounds' documents then you must create an async function that you should call on your main function. See code sample below:
const roundRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds"));
const unsub = onSnapshot(roundRef, (querySnapshot) => {
querySnapshot.forEach((doc) => {
const DocId = doc.id;
// Calls the `runQuery` async function to get the exercises documents.
runQuery(DocId).then((result) => {
// Initialized an object
const obj = {};
// Assigns the exercises' documents data to its round collection's documents' Id.
obj[DocId] = result;
// Logs the constructed object.
console.log(obj);
// Put your `innerhtml` here.
});
});
});
async function runQuery(documentId) {
const exerciseRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds", documentId, "exercises"));
const querySnapshot = await getDocs(exerciseRef);
if (!querySnapshot) {
return null;
}
// Returns a map of data (array).
return querySnapshot.docs.map((doc) => doc.data());
}
I've added some comments to better understand the code above.
As I've pointed out some of the issues above, there can be some workarounds if you want to get a real-time update from your rounds collection. You must modify how you add, update, or delete documents. First, you need to add a document to the exercises collection and later update or set data to the newly created rounds' document. See sample code below:
// Your document ID for rounds collection
const docId = 'Round 3';
const exercisesRef = collection(db, 'user', uid, 'yourWorkouts', id, 'rounds', docId, 'exercises');
const roundsRef = doc(db, 'users', uid, 'yourWorkouts', id, 'rounds', docId);
// Add a document to the rounds and exercises collection. Sets the data to the exercises document.
addDoc(exercisesRef, {
// Your exercises document data
}, { merge: true })
.then (() => {
// Method above will create a round document but doesn't have data on it (still an invalid document).
// We need to set or update the document to make it a valid document to be able for a snapshot to read the newly added document.
setDoc(roundsRef, {
// Your Rounds document data
}, { merge: true })
.catch((error) => {
console.error("Error writing Rounds document: ", error);
});
})
.catch((error) => {
console.error("Error writing exercises document: ", error);
});
For more relevant information, you may want to check out this documentations:
Update:
Based on how you render the data, you're just assigning an object to a variable which in turn returns undefined. You must access the object first and use the for loop as exercises collections can have more than 1 document. See code below:
// Create Exercise Rounds
const Rnds = document.getElementById("rnds");
const roundRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds"));
const roundsUnsub = onSnapshot(roundRef, (querySnapshot) => {
querySnapshot.forEach((doc) => {
const DocId = doc.id;
const rounds = doc.data().roundName;
const rests = "Rest: " + doc.data().rest;
// Calls the `runQuery` async function to get the exercises documents.
runQuery(DocId).then((result) => {
// Initialized an object
const obj = {};
// Assigns the exercises' documents data to its round collection's documents' Id.
obj[DocId] = result;
// Initialize names array to be used in `innerhtml`
const names = [];
// Logs the constructed object.
console.log(obj);
// Loops your exerciseNames and push it in the `names` array
for (const exerciseDocument of obj[DocId]) {
names.push(exerciseDocument.exerciseNames);
}
// Put your `innerhtml` here.
Rnds.innerHTML += `
<h2>${rounds}</h2>
<p>${names}</p>. // This will now show
<p>${rests}</p>
`
});
});
});
async function runQuery(documentId) {
const exerciseRef = query(collection(db, "user", uid, "yourWorkouts", id, "rounds", documentId, "exercises"));
const querySnapshot = await getDocs(exerciseRef);
if (!querySnapshot) {
return null;
}
// Returns a map of data (array).
return querySnapshot.docs.map((doc) => doc.data());
}
roundsUnsub;
To separate the names, instead of pushing names into an array, you need to initialize a string and iterate a html output with <p> tag and use += (Addition assignment operator) to assign the result to the variable. See code below:
runQuery(DocId).then((result) => {
// Initialized an object
const obj = {};
// Assigns the exercises' documents data to its round collection's documents' Id.
obj[DocId] = result;
var names = "";
for (const exerciseDocument of obj[DocId])
// Make sure that you type the fieldname correctly.
// Based on your codes given above, you mentioned `exerciseName` and `exerciseNames`
names +=`<p>${exerciseDocument.exerciseNames}</p>`;
}
Rnds.innerHTML +=
`<h2>${rounds}</h2>.
${names}.
<p>${rests}</p>`
});
});
Check out this working snippet. It should be something like this:
<!DOCTYPE html>
<html>
<body>
<p id="rnds"></p>
<script>
const Rnds = document.getElementById("rnds");
const DocId = 'Round'
const rounds = DocId;
// Sample constructed object based on your Firestore Structure
const obj = {Round :[{exerciseNames: 'exercise1'}, {exerciseNames: 'exercise2'}]};
const rests = 'testRest';
var names = ""
for (const exerciseDocument of obj[DocId]) {
names +=`<p>${exerciseDocument.exerciseNames}</p>`;
}
Rnds.innerHTML +=
`<h2>${rounds}</h2>.
${names}.
<p>${rests}</p>`
</script>
</body>
</html>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
