Hey I try to fix a Problem I need to get a subcollection based on a query before.
const kurzRef = collectionGroup(db, 'kurzwaffensub' );
const FirstOperation = query(kurzRef,where("kurzModell", "==", `${kurzModell}` ));
const getWaffenDaten = async () => {
const modell = await getDocs(FirstOperation);
const data = [];
for (const doc of modell.docs) {
const parentDoc = await getDoc(doc.ref.parent.parent);
const { Name, avatarPath } = parentDoc.data();
// Thats the code snippet which I have my problem with
const waffenbilderRef = collection(db, 'users', doc.data().uid, 'waffenbildersub')
let subCollectionDocs = await getDocs(waffenbilderRef)
//
data.push({
...doc.data(),
Name,
subCollectionDocs,
avatarPath
});
The documents which I get with the first operation have a String Fieldvalue of the document ID
After that I need to get the subcollection based on the Fieldvalue document ID
Which is one of 3 subcollections which you can see in the picture.
Unfortunately I get something back which I dont fully understand
As you can see I get the subCollectionDocs, but it doesn't display the Data.
You can see next to the RED marked Arrow that there are 2 Documents in the result of the subcollectionDocs. This is right but I don't know how to retrieve the data properly.
You're adding the full DocumentSnapshot from the subcollection query to your state, which is what then shows when you console.log it.
My guess is that you're expecting to just see the document's data and ID, which you can do with:
const subCollectionDocs = await getDocs(waffenbilderRef)
const subCollectionData = subCollectionDocs.docs.map((doc) => {
return { id: doc.id, ...doc.data() };
}
console.log(subCollectionData);
data.push({
...doc.data(),
Name,
subCollectionData,
avatarPath
});
Related
I have a collection called display and each document inside the display has a subcollection called history. There's no error, though, it does not delete the documents in Firestore. After selecting the row to be deleted, it is gone when you click delete. However, once you reload the screen, the data deleted was still there, meaning it wasn't successfully deleted in Firestore.
I recreated the error I have in the code sandbox: https://codesandbox.io/s/batch-delete-not-working-vcqcd3?file=/src/App.js
async function batchDeleteDocuments(docID, historyId) {
try {
console.log(docID, "docs");
console.log(historyId, "history");
const batch = writeBatch(db);
for (let i = 0; i < docID.length; i++) {
const docRef = doc(db, "display", docID[i], "history", historyId[i]);
console.log(i, "deleting", docRef.path);
batch.delete(docRef);
}
await batch.commit();
console.log("deleted");
} catch (err) {
console.log(err);
}
}
Upon checking your codesandbox, the field docID has whitespace at the beginning of the string. See screenshot of your codesandbox logs:
Digging deeper into your code, I've found no issues with how you fetch your data. It's just that when you try to log your doc.data() in this query:
const getUsers = async () => {
const listUsers = query(collectionGroup(db, "history"));
const querySnapshot = await getDocs(listUsers);
const arr = [];
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
arr.push({
...doc.data(),
id: doc.id
});
});
if (isMounted) {
setUsers(arr);
}
};
The value of docID on your document has whitespace on it. Check the values of docID in your documents of the history collection and make sure that you removed all the whitespace on it.
I also tried to replace the docID[i] in this query and successfully deleted the document.
// Try to change docID[i] to hard-coded value.
const docRef = doc(db, "display", "Tv9xj0pC9wTjr59MPsJw", "history", historyId[i]);
You can also use the trim() method for the workaround. See code below:
for (let i = 0; i < docID.length; i++) {
console.log(docID[i].trim());
const docRef = doc(
db,
"display",
docID[i].trim(),
"history",
historyId[i]
);
console.log(i, "deleting", docRef.path);
batch.delete(docRef);
}
I'm trying to add a delete button to my page. the event listener callback is working properly except for the updateDoc function.
const deleteBook = document.getElementsByClassName('deleteBook');
for (let i = 0; i < deleteBook.length; i++) {
deleteBook[i].addEventListener('click', async () => {
//book to delete
const bookToDelete = deleteBook[i].parentElement.firstElementChild.textContent
// collection title to delete the book from
const bookCol = deleteBook[i].parentElement.parentElement.parentElement.firstElementChild.textContent
// get a snap of the database
const docRef = doc(dataBase, 'users', `${auth.currentUser.uid}`)
const docSnap = (await getDoc(docRef)).data();
// loop over the collections and get a match with the bookCol
for (const col in docSnap) {
if (docSnap[col].title === bookCol) {
console.log('col to delete from found')
console.log(`book to delete ${bookToDelete}`)
await updateDoc(doc(dataBase, 'users', `${auth.currentUser.uid}`), {
[`${col}.books`]: arrayRemove(`${bookToDelete}`)
}).then(()=>{
// fullfiled
console.log('book deleted')
}, ()=>{
// rejected
console.log('promis rejected')
})
}
}
})
}
Col is the object that contains the books array. In the console it always prints book deleted, but in the firestore console, nothing changes. this is a screenshot of the database.
I would really appreciate any help and thank you.
I have replicated the behavior that you're experiencing. I tried changing the content of ${bookToDelete} to any word or even ID. It always returns book deleted even if its deleted or not. The line of code below should be changed in order to get the correct output.
.then(()=>{
// fullfiled
console.log('book deleted')
}, ()=>{
// rejected
console.log('promis rejected')
})
I have created a workaround for your use-case with this kind of issue. See snippet below:
const db = getFirestore();
const colName = "users";
const arrayName = "books";
const usersCol = collection(db, colName);
const userRef = doc(db, colName, `${auth.currentUser.uid}`);
const arrayRef = `${col}.${arrayName}`;
const q = query(usersCol, where(arrayRef, "array-contains", `${bookToDelete}`));
const querySnapshot = await getDocs(q)
.then((querySnapshot) => {
// Removal of object will not proceed if the querySnapshot is empty.
if ((querySnapshot.empty)) {
console.log("No object found!");
}
else {
// Proceeds to removal of object.
updateDoc(userRef, {
[arrayRef]: arrayRemove(`${bookToDelete}`)
})
.then(() => {
// Check again if the object was deleted successfully.
const querySnapshot = getDocs(q)
.then((querySnapshot) => {
if ((querySnapshot.empty)) {
console.log("Book Deleted!");
}
else {
console.log("Failed!");
}
})
});
}
})
// Catch if there are any Firebase errors.
.catch(error => console.log('Failed!', error));
The workaround that I created will query the object in the array then remove the object in the array if it exist. After removing, it will query again to check if the object has been deleted and logs Book Deleted!. Vise versa for checking if the object doesn't exist on the 1st query, it will not proceed on removing them and logs No object found!.
The workaround itself can still be improved. You can add any logic you want for your use-case.
I'd also recommend to create a Feature Request if you want to have this kind of feature together with the arrayRemove Method.
I have been using firebase (firestore) for a while but I'm a little stuck and was wondering if anyone can think of a solution.
On the firestore DB I have a single collection of users, each user has an email address and several other fields. In this instance I am checking if a user email exists and if it does, I want to create a list field for that particular user with a listUid. I am referencing the users by email, grabbing the docId for those users and then trying to set a list field for each of them.
I am not getting any error's from firestore, it's simply not updating in the DB for some reason and I can't figure out where I am going wrong. Thanks in advance
export const addListUidToExistingUserList = (
{ firestore },
emailArray,
listUid
) => {
return async () => {
let docIds = [];
emailArray.forEach((emailAddress) => {
//find users by email (works)
const query = db
.collection("users")
.where("email", "==", emailAddress);
//get docId's for user with matching email (works)
query.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
docIds.push(doc.id);
});
});
//add a new list with corresponding listUid (does not work)
docIds.forEach((id) => {
let userRef = db.collection("users").doc(id);
batch.set(userRef, { lists: [{ listUid }] });
});
});
return await batch.commit();
};
};
You are running into this issue because your docIds array is always empty at the time you call docIds.forEach.
That's because query.get().then runs asynchronously, and so docIds.forEach is not waiting for it to complete.
You could either:
await query.get().then; or
Add the docIds.forEach function INSIDE the then callback of query.get.
Here are your possible fixes:
await query.get().then
//get docId's for user with matching email (works)
await query.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
docIds.push(doc.id);
});
});
OR:
docIds.forEach inside then
//get docId's for user with matching email (works)
query.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
docIds.push(doc.id);
});
docIds.forEach((id) => {
let userRef = db.collection("users").doc(id);
batch.set(userRef, { lists: [{ listUid }] });
});
});
Note: Of course, you could also add batch.set directly into your first iteration of querySnapshot.docs.forEach to prevent an unnecessary iteration.
I am querying firebase firestore by...
let database = firebase.firestore();
let places = database.collection("place");
console.log("places", places);
now the logged data is bizarre and not the actual documents..
here is a picture of the log...can you please advice regarding tackling this ?
If you want to retrieve all items in your collections called "place" you can do something like this:
let database = firebase.firestore();
let places = database.collection("place");
const querySnapshot = places.get()
// You can make an empty array to eventually push the items into
const collectionArray = []
querySnapshot.forEach((doc) => {
const data = doc.data()
collectionArray.push(data)
}).catch(function(error) {
console.log("Error getting documents: ", error);
})
console.log('collectionArray:',collectionArray)
}
Your code hasn't actually executed any query yet. All it's done is build a Query object.
If you want to execute the query, call get() on it, and handle the results as shown in the documentation.
let database = firebase.firestore();
let query = database.collection("place");
query.get()
.then(querySnapshot => {
querySnapshot.forEach(documentSnapshot => {
console.log("document", documentSnapshot.data());
})
})
i'm having this problem where I can't make the startAfter work with my data in Firestore.
I'm giving this two examples of the issue the first image is when it works, filtering with a property(createdAt), the second passing the whole doc returns empty and can't make the forEach to loop through the data
Does some know what this happen ? the documents do not have any complex information, name, creation date, all numbers for testing.
If someone had this problem, please help me with an answer, just started learning Firebase a few days ago.
Thanks in advance :)
// getting the data
const response = await db
.collection("apis")
.orderBy("createdAt")
.limit(3)
.get();
const dataSend = [];
response.forEach((document) => {
dataSend.push(document.data());
});
//triggering the next data load
const getMore = async () => {
const limit = 3;
const last = apis[apis.length - 1]; // last document
console.log(last); // {name: "3", description: "3", createdAt: t, url: "3", authorId: 123123, …}
try {
const response = await db
.collection("apis")
.limit(limit)
.orderBy("createdAt")
.startAfter(last.createdAt) // passing createdAt to fix the problem
.get();
console.log(response);
const dataSend = [];
response.forEach((document) => {
//this is not entering here
dataSend.push(document.data());
});
} catch .....
SECOND CASE
// getting the data
const response = await db
.collection("apis")
.orderBy("createdAt")
.limit(3)
.get();
const dataSend = [];
response.forEach((document) => {
dataSend.push(document.data());
});
//triggering the next data load
const getMore = async () => {
const limit = 3;
const last = apis[apis.length - 1]; // last document
console.log(last); // {name: "3", description: "3", createdAt: t, url: "3", authorId: 123123, …}
try {
const response = await db
.collection("apis")
.limit(limit)
.orderBy("createdAt")
.startAfter(last) // PASSING THE WHOLE DOCUMENT AS A PARAMETER DO NOT WORK
.get();
console.log(response);
const dataSend = [];
response.forEach((document) => {
//this is not entering here
dataSend.push(document.data());
});
} catch .....
The solution to this problem was that I was getting the data and not the doc reference.
To fix something like this you'll have to add to the code something like this
response.docs[response.docs.length - 1]
// getting the data
const response = await db
.collection("apis")
.orderBy("createdAt")
.limit(3)
.get();
const dataSend = [];
const last = response.docs[response.docs.length - 1] // this is the reference to the doc that the documentations says
response.forEach((document) => {
dataSend.push(document.data());
});
//triggering the next data load
const getMore = async () => {
const limit = 3;
const last = response.docs[response.docs.length - 1] // last document
console.log(last); // {name: "3", description: "3", createdAt: t, url: "3", authorId: 123123, …}
try {
const response = await db
.collection("apis")
.limit(limit)
.orderBy("createdAt")
.startAfter(last) //
.get();
console.log(response);
const dataSend = [];
response.forEach((document) => {
//this is not entering here
dataSend.push(document.data());
});
} catch .....
So instead of passing the last object from the database you pass the last reference to the doc before it transforms with the data() function that Firebase provides.
ALSO it works better than passing the object.createdAt.
REFERENCE
https://firebase.google.com/docs/firestore/query-data/query-cursors
Actually the reason that the first code block is running is because that is the correct usage of startAt().
As you can see on the examples in the Official Documentation, you should use a value in startAt() and never a full document, and that actually makes sense if you consider that you are sorting the data by a specific field and you should also start your results by a specific value on that same field.
So the correct usage is indeed .startAfter(last.createdAt) in your case.