How to query in a specific document in Firebase - javascript

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.

Related

Better way to get document ID which was auto generated to create the doc or update an array inside the document Firestore / Firebase

I'm creating one document per user to store the id's of the users bookmarks. I'm using addDoc so the ID of the created doc is generated by firebase. I want to check if that document exists to create a new one or update the old. I need to have a document reference to update the doc and I had a horrible time trying to figure out a way to get a hold of that auto generated ID. My code works but it feels really hacky - Is there a better way to accomplish this?
//Add a value to the bookmarks array for individual user
export const addBookmarkForUser = async (userAuth, showId) => {
const bookmarkDocRef = collection(db, 'users', userAuth.uid, 'bookmarks')
const bookmarkSnapshot = await getDocs(bookmarkDocRef)
let userBookmarkDocId
if(bookmarkSnapshot){
bookmarkSnapshot.forEach((doc) => {
if(doc.id){
userBookmarkDocId = doc.id
console.log(userBookmarkDocId)
}
})
}
try {
if(!bookmarkSnapshot) {
await addDoc((collection(db, 'users', userAuth.uid, 'bookmarks'), {
favorites: [{showId: showId}],
}))
}else {
const userBookmarkRef = doc(db, 'users', userAuth.uid, 'bookmarks', userBookmarkDocId)
await updateDoc(userBookmarkRef, {
favorites: arrayUnion({showId: showId})
})}
} catch (error) {
console.log('Error creating bookmark', error.message);
}
};
I know I will only have one document in there for each user - is there a better way to find the doc.id?
Here is the firestore structure -
If you just want to check how many documents are present in a collection then you can use getCountFromServer() that loads the count without the actual document data.
import { collection, getCountFromServer } from 'firebase/firestore';
const bookmarkColRef = collection(db, 'users', userAuth.uid, 'bookmarks');
const bookmarksCount = (await getCountFromServer(bookmarksColRef)).data().count
console.log(`${bookmarksCount} documents found in ${bookmarksColRef.path}`)\
if (!bookmarksCount) {
// no document exists, create new
}
However, since you need the document ID and its data. You can run a simple query:
import { collection, getDocs } from 'firebase/firestore';
const bookmarkColRef = collection(db, 'users', userAuth.uid, 'bookmarks');
const bookmarksSnap = await getDocs(bookmarksColRef);
if (bookmarksSnap.empty()) {
// no document
} else {
const docData = bookmarksSnap.docs[0].data();
console.log(docData)
}

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.

Trouble batch setting a document field by docId in Firestore

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.

Firestore query with where returning undefined snapshot (Vue.js / Firestore / Vuefire)

I'm currently using Firestore/Vue.js with Vuefire support.
The Firestore DB has just one collection with a couple of users inside:
Users
001
name: Jack
uid: {Firebase auth ID}
org_id: 123
002
name: Frank
uid {Firebase auth ID}
org_id: 456
in the Vue component, I try to query the DB to get the first user using the auth ID (which is currently stored in a Vuex Store)
<script>
import { db } from "../main.js";
export default {
data() {
return {
items: [] // to be used later
};
},
created() {
this.getServices();
},
methods: {
getServices() {
console.log(this.$store.state.user.uid);
db.collection("users")
//.doc("001")
.where("uid", "==", this.$store.state.user.uid)
.get()
.then((snapshot) => {
console.log(snapshot);
if (snapshot != null && snapshot.data != null) {
const user = snapshot.data();
// do something with document
let org_id = user["org_id"];
console.log("org id:" + org_id);
} else {
console.log("No data returned!");
}
});
},
},
};
</script>
the code always returns an empty snapshot. Checks I have performed:
accessing the document directly using its doc ID works
this.$store.state.user.uid is correctly set
hard-coding the uid in the where clause gives the same error
I'm a total beginner but it looks to me the where clause is not working.
Since, with db.collection("users").where("uid", "==", this.$store.state.user.uid) you define a Query, the snapshot Object is actually a QuerySnapshot and not a DocumentSnapshot.
So snapshot.data != null is always false because a QuerySnapshot does not have such a property. It is also the case for snapshot.data() != null => it is always false because a QuerySnapshot does not have such a method.
You should either loop over the QuerySnapshot with the forEach() method or use map on the docs property, as shown in the Vuefire example (see "retrieve a collection"):
db.collection("users")
.where("uid", "==", this.$store.state.user.uid)
.get()
.then((snapshot) => {
const documents = snapshot.docs.map(doc => doc.data())
// do something with documents
})

Categories

Resources