Get fields from nested DocumentReference item in Firestore - javascript

I'm in the process of writing an API using firebase functions: api is written in javascript.
In my firestore db, I have a user document that contains some nested fields. For example, my user doc has a field that looks roughly like this:
profile
education
education1 --> degree: doc ref, date: timestamp, school: doc ref
education2 --> degree: doc ref, date: timestamp, school: doc ref
I cannot for the life of me access the degree object and get the properties out of it. Each user could have multiple education entries (for example, people who hold multiple degrees). I can't step into those education# maps and access the fields inside the document they are referring to.

You didn't give a lot of details on your exact data model: which collection, which document, etc...
However, since in your comment above you say that "profile is a map, education is a map that lives inside of profile, and education items are also maps that live inside of education" the following should do the trick
var docRef = firestore.collection('collectionId').doc('docID');
docRef
.get()
.then(doc => {
if (doc.exists) {
const educationObj = doc.data().profile.education;
const promises = [];
Object.keys(educationObj).forEach(key => {
promises.push(firestore.doc(educationObj[key].degree.path).get());
});
return Promise.all(promises);
} else {
// doc.data() will be undefined in this case
console.log('No such document!');
throw new Error('no doc');
}
})
.then(results => {
results.forEach(r => {
console.log(r.data());
});
})
.catch(error => {
console.log('Error getting document:', error);
});
The degree property contains a DocumentReference, therefore you need use the path property in order to get the corresponding document.

Related

Why can't I get the sub-collection items in firebase in react in 2022

So i've been researching about how do i get the subcollection of firebase documents. Basically its .get() but its not working now in 2022 I THINK. I have the code in below...
Let say this one I will create a subcollection path with the collections.
await setDoc(doc(db,list2[i],`${currentUser?.email}-${uid}`,`single_item`,`image`),{
creator:username,name:name,img:downloadURL,email:currentUser?.email
})
await setDoc(doc(db,list2[i],`${currentUser?.email}-${uid}`,`group_item`,`images`),{
creator:username,name:name,img:downloadURL,email:currentUser?.email
})
Now i'm getting all the items of firebase documents with these...
export const owneritemsRef = collection(db,'owner_items')
export const singleItemsRef = collection(db,'owner_items/single_item/image')
export const groupItemsRef = collection(db,'owner_items','group_item',`images`)
Now when I tried to read it in my react file...
useEffect(() => {
console.log(singleItemsRef)
const unsubscribe = onSnapshot(singleItemsRef,snapshot => {
console.log(snapshot)
setSearchFilter(snapshot.docs.map((doc,idx) => {
console.log(doc.data())
return {
...doc.data(),
name:doc.data().name
}
}))
setSearchList(snapshot.docs.map((doc,idx) => {
console.log(doc)
return {
...doc.data(),
name:doc.data().name
}
}))
})
return () => {
unsubscribe()
}
},[])
It doesn't show anything...like it is completely null.. but I can see the pathsegments of singleRef... How do I get those documents please? The diagram is like this
owner-items -> (single/group) -> image/s -> { document items }
A collection group consists of all collections with the same ID and the subcollections live under a specific document. To access the subcollection you'll need to specify that particular ID in the path reference.
Using Collection Group Queries might be the easiest way to fetch all documents from collections with the same name which are passed in the collectionGroup() method.
You need to use collection() to get a CollectionReference instead of doc() which returns a DocumentReference.
Also check these similar examples for Get Subcollection and fetching subcollection documents.
UPDATE
If you need to get that specific nested subcollection you could try something similar to below
db.collectionGroup("orders").get().then((querySnapshot) => {
console.log(querySnapshot.docs.map(d => ({id: d.id, ...d.data()})))
})

How to list all documents in a sub-collection and then fetch their documents by their ID?

I'm trying to show data from a document inside a collection between "students" and their "courses"
I'm using html, javascript and this structure but as of now I can only retrieve the ID of the document but I'd like to also access the data inside this document
async function fetchCourses() {
const studentId = firebase.auth().currentUser.uid
const courseIds = await db.collection(`students/${studentId}/attending`).get();
const courseDocs = await Promise.all(
courseIds.docs.map(doc => db.doc(`courses/${doc.id},${studentId}`).get())
);
return courseDocs.filter(doc => doc.exists).map(doc => ({ id: doc.id, ...doc.data() }),
console.log(courseDocs));
}
My console.log displays all this:
Any help is gladly appreciate
Edit
This is the data structure inside my Firebase:
The students are stored in a collection indexed by their uid and each student document contains their name and more data, but most importantly a sub-collection named attending which contains empty documents indexed by courseId, refering to a document in the courses collection
The courses are stored in another collection indexed by courseId and like in students each course document contains a title and more data, and a sub-collection named attendees which contains empty documents indexed by uid, refering to a student.
import { doc, getDoc } from "firebase/firestore";
const docRef = doc(db, "cities", "SF");
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
above is an example from firestore docs. firestore has really specific syntax.
you can only access data of request with doc.data() syntax. All other attempts will bring you nothing or some irrelevant object(at least in my experience)

Firebase Cloud Function. Create documents in sub-collection of specific collections

here's the basic premise of what im trying to accomplish here. if a user ask a question about a product i want to send a notification to other users who currently own that product. basically saying "hey, so and so has a question about this product. maybe you can help since you own it already"
each userProfile collection has a subcollection called 'notify' where notifications are stored for various things. what i need to do is sort through the userProducts and find every user who owns the product and then create a notification post in only the notify sub-collections for those specific users who own that product.
here is the basic code. the first bit works in that it does return an array of userIDs who own that product. where im struggling now is getting it to create a new doc in the Notify sub-collection for just those specific users. is this possible to do?
exports.Questions = functions.firestore
.document("/userPost/{id}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (data.question == true) {
const userProducts = await db
.collection("userProducts")
.where("product", "==", data.tag)
.get();
const userData = userProducts.docs.map((doc) => doc.data().userId);
await db
.collection("userProfile")
.where("userId", "in", userData)
.get()
.then((querySnapshot) => {
return querySnapshot.docs.ref.collection("notify").add({
message: "a user has asked about a product you own",
});
});
});
Your current solution is on the right track, but there are improvements that can be made.
Use a guard pattern for the data.question == true check.
You don't need to get userProfile/<uid> as you aren't using its contents.
When changing multiple documents at once, you should consider batching them together for simpler error handling.
ref.add(data) is shorthand for ref.doc().set(data) which you can use in the batched write to create new documents.
exports.Questions = functions.firestore
.document("/userPost/{id}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (!data.question) {
console.log("New post not a question. Ignored.")
return;
}
const userProducts = await db
.collection("userProducts")
.where("product", "==", data.tag)
.get();
const userIds = userProducts.docs.map(doc => doc.get("userId")); // more efficient than doc.data().userId
// WARNING: Limited to 500 writes at once.
// If handling more than 500 entries, split into groups.
const batch = db.batch();
const notificationContent = {
message: "a user has asked about a product you own",
};
userIds.forEach(uid => {
// creates a ref to a new document under "userProfile/<uid>/notify"
const notifyDocRef = db.collection(`userProfile/${uid}/notify`).doc();
batch.set(notifyDocRef, notificationContent);
});
await batch.commit(); // write changes to Firestore
});
Note: There is no special handling here for when no one has bought a product before. Consider pinging the product's owner too.

Firebase Collection Group Query in VueJS

created() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.userID = user.uid;
console.log(this.userID);
} else {
console.log('User is not logged in.');
this.$router.push('/login');
}
});
},
data() {
return {
pantry: [],
userID: '',
};
},
methods: {
getCategoryDataFromFirebase() {
const db = firebase.firestore();
db.collectionGroup("pantry")
.where("UserID", "==", this.userID)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.pantry.push(doc.data());
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
},
},
};
I am attempting to do a Collection Group Query where I grab all of the listings in my firestore database that are associated with the user ID that created them. I am able to get the user ID on the created hook and store it in my this.userID. I want to then use that ID on my getCategoryDataFromFirebase function to do the collection group query.
It's recommended to create the collection group query and get the SDK error back from firebase in the console, then use the attached link in the console to create the appropriate rules automatically instead of manually which I did. Now I am thinking that I must not be referencing the group correctly because I am unable to get any data back from my firestore. I have tried to create the group by the main "pantry" but I am thinking that I would possible need to drill down further into the database or to set up the query another way. I would appreciate any guidance that could be given. I have attached the view of my firestore as well for reference. I am attempting to get all of the userIDs in each category i.e. fruits, vegetables, etc.
When you use db.collectionGroup("pantry") you are reading from all collections named pantry. In your code that is only a single top-level collections.
If you want to read from all Vegetables collections, you need to query db.collectionGroup("Vegetables").

Cannot fetch list of UIDs inside "users" collection in firebase using reactjs

constructor(props) {
super(props);
this.state = {
users:[]
};
}
//method to get the data from users collection
async componentDidMount() {
const db = firebase.firestore();
db.collection("users")
.get()
.then(querySnapshot => {
const data = querySnapshot.docs.map(doc => doc.data());
console.log(data);
this.setState({ users: data });
})
.catch( err =>{
console.log(err);
});
}
this function returning an empty error
i want to print list of users uid
here i have users collection and inside it i have retailers collection and its document
One thing you have to realize here is that your users collection contains no documents. The document IDs are shown in italics, which means that there is no document here. The IDs are visible because there is a nested subcollection under each document. They are shown like this in the console so that you can click through and navigate to the nested subcollection, despite the document being missing.
If you want to list users with a query, you will have to actually create documents in the users collection. They can be empty if you don't have any information. But you do need actual documents in the users collection in order for anything to show up in a query.
This line in your current code querySnapshot.docs.map(doc => doc.data()) takes the data of each document. But you're keeping the UID in the ID of each document, so you'll want to use:
const db = firebase.firestore();
db.collection("users")
.get()
.then(querySnapshot => {
const data = querySnapshot.docs.map(doc => doc.id);
console.log(data);
this.setState({ users: data });
})
.catch( err =>{
console.log(err);
});
Update: As Doug pointed out in his answer, if there are no actual user documents, your get() call will not return it.
I highly recommend creating user documents, even if it's just an empty one.
For now, the only way to get the UID would be to load all retailers for all users, and then use those results to get the ID of the parent documents:
const db = firebase.firestore();
db.collectiongroup("retailers")
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
console.log("retailed "+doc.id+" for user "+doc.ref.parent.parent.id);
});
})
.catch( err =>{
console.log(err);
});
You'll have to deduplicate the UIDs, but that will leads to getting the UIDs.
But you'll be loading all retailers for all users this way, so as said, I highly recommend storing a small/empty user document instead.
The data method on querySnapshot.docs gets the data now to get the id of each document you need to add access the id property as UID is stored in id property
async componentDidMount() {
const db = firebase.firestore();
db.collection("users")
.get()
.then(querySnapshot => {
const data = querySnapshot.docs.map(doc => doc.data().id);
console.log(data);
this.setState({ users: data });
})
.catch( err =>{
console.log(err);
});
}

Categories