Unable to batch.delete documents of a subcollection - javascript

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);
}

Related

How to query in a specific document in Firebase

I have some code that gets a collection reference to the users collection and then queries an animeID field whether it contains a certain value or not. I want to change this and only query inside the document with the id i pass. So if you look at the picture of my firestore you can see that i have two documents inside the users collection each with their unique id. I want to query for the animeID field only in the document that i want. Not all the documents as it does right now. How would i go about doing this? I have tried using doc and then passing in the id of the document but i don't think query works on doc as it gives me an error. Thanks
const docRef = collection(db, 'users')
const q = query(docRef, where('animeID', 'array-contains', parseInt(id)))
onSnapshot(q, (snapshot) => {
let results = []
snapshot.docs.forEach((doc) => {
results.push({...doc.data(), id: doc.id})
})
if(results.length > 0){
console.log(true)
}
else{
console.log(false)
}
}, (error) => {
console.log(error)
})
Firestore structure:
You need to do as explained in the doc:
import { doc, getDoc } from "firebase/firestore";
const docRef = doc(db, "users", "dbmbEiR6....");
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const animeID = docSnap.data().animeID;
// Do whatever you want with animeID
// E.g. log its value:
console.log(JSON.stringify(animeID));
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
So you don't need to declare a Query in this case. Just declare a DocumentReference and use the getDoc() method.

Make query with "where" filter in Firestore

I want to filter and get specific items based on the categoryID, where i use the "where" method and the "==" operator.
I have this Firestore collection called "task", where the documents are divided by userID and each document for a user contains an array of different tasks. The structur is seen here:
Enter image description here
This is how i add the array tasks for an user in the task collection:
const addToFirebase = async() => {
if(dataFetch) {
await setDoc(doc(db, "task", `${firebase.auth().currentUser.uid}`), {
tasks: tasks
}, {merge: true});
}
}
And this is how i have tried to make my query that is not working:
const getFilteredTasks = async() => {
const collectionRef = collection(db, "task", `${firebase.auth().currentUser.uid}`, "tasks");
const q = query(collectionRef, where("categoryID", "==", item.id));
console.log('outside snapshot')
console.log(item.id)
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.data());
console.log('inside snapshot')
});
}
When the above function is called, it logs the "outside snapshot" and the correct item.id, which is the categoryID for each task, but it does not log the doc.data() and the "inside snapshot".
You've set up your query as though each task will be a separate document in a collection, but based on the screenshot and the way you're setting the data, you just have a single document per user. That document contains all the individual tasks. So to access that you would get the document and use client side code to use the parts of the data you care about:
const getFilteredTasks = async () => {
const docRef = doc(db, 'task', `${firebase.auth().currentUser.uid}`);
const snapshot = await getDoc(docRef);
const data = snapshot.data();
if (!data) {
return [];
} else {
return data.tasks.filter(task => task.categoryID === item.id);
}
}

how to remove an array item from firestore using JavaScript?

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.

Problem with request from Subcollection in Firestore

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
});

Firestore startAfter method is not working with a document as a reference

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.

Categories

Resources