I'm building a chat app. When a user makes an update on their local profile I'd like to use cloud functions to make that update across a collectionGroup.
I'm successfully listening to the update in cloud functions and retrieving a list of collectionGroups with the following:
const collectionGroupNameref = await db.collectionGroup('collectionGroupName').where('userId', '==', data.uid).get();
collectionGroupNameref.forEach(async (val: any) => {
const connectionsRef = await db.collection('collectionGroupName').doc(val.id).get();
});
But now I need to update a field within that collectionGroup and that's where I'm running into issues.
The collectionGroup is stored in 2 locations:
users{id}collectionGroupName{id}
groups{id}collectionGroupName{id}
Is it possible to update all of the documents in that collectionGroup
Firestore doesn't provide any methods to update an entire collection or collection group like "UPDATE WHERE" in SQL. What you are required to do instead is write each document individually. So, if you've already executed a query for documents in the collection group, can you simply iterate the documents in the result set and update each document as needed. You can use the ref property of DocumentSnapshot to easily update each document, no matter what collection contains it.
const querySnapshot = await db
.collectionGroup('collectionGroupName')
.where('userId', '==', 'data.uid')
.get();
querySnapshot.docs.forEach(snapshot => {
snapshot.ref.update(...)
})
Related
Let's say, I need to send an email to all the members of the group when the group is complete:
export const sendEmailToNewMember = functions.firestore.document('group/{groupId}').onCreate(async (snapshot,context) => {
}
The collection group has subcollection members. I check if the member's count has incremented and matches the total number of members who can join a group. If the final member has joined, I need to send all the members an email saying the group is complete.
The data structure looks like the one shown below:
collection group {
membersCount: 9999,
totalMembers: 100000,
....
users subcollection
}
I tried accessing the collection through ref like so:
const usersRef = snap.ref.collection('users)
But, the result was undefined. For now, I access it using
await firestore().collection('group').doc(groupId).collection('users')
Is it possible to access the subcollection without awaiting it and from the snapshot itself?
The document() takes path to a document and not a field. So if you want to trigger this function when any of the fields in group document is updated then set the path to .document('group/{groupId}').
Also snapshot in onCreate() will then contain data of that group's document only (the document that triggered the function). You'll have to explicitly read the sub-collection.
It seems you want to send email to new member that joins a group. You can instead trigger the function when a new user is added in user's sub-collection as shown below:
// Here user's UID would be ID of sub-document
export const sendEmailToNewMember = functions.firestore.document('group/{groupId}/users/{userId}').onCreate(async (snapshot,context) => {
const { groupId, userId } = context.params;
}
I want to obtain the value of a collection ID from a collection in cloud firestore if it exists:
export const getSlugs = async () => {
const document = await db
.doc(constDocumentRefs.slugs)
.collection('<collection_id>')
return ;
};
but this returns me collection reference, I can check if its empty by calling: document.get().empty method but not sure how do I get the value of collection, in case it is not empty.
My collection looks like this:
{
key1:1
key2:2
}
I want to keep it like return the actual value if collection exists otherwise return -1. Someone please help!
I can see two possible ways:
From the front-end:
As Dharmaraj mentioned in his comment, you need to fetch document(s) in the collection to see if the querySnapshot is empty or not. If the snapshot is empty, the collection does not exist. You can limit the query to only one document to minimize cost. For that you'll use the limit() method. And for checking if the QuerySnapshot contains a doc use the size property.
From a back-end:
The Admin SDKs offer a specific method to list collections, for example listCollections() for the Node.js Admin SDK (and listCollections() method of a DocumentReference for listing sub-collections). You can implement that in a Cloud Function and call it from your front-end: I wrote an article on this approach.
I have some code that looks like the following:
export const createTable = async (data) => {
const doc = db.collection("tables").doc();
const ref = db
.collection("tables")
.where("userId", "==", data.userId)
.orderBy("number", "desc").limit(1);
db.runTransaction(async transaction => {
const query = await transaction.get(ref);
let number = 1;
if (!query.empty) {
const snapshot = query.docs[0];
const data = snapshot.data();
const id = snapshot.id;
number = data.number + 1;
}
data = {number, ...data};
transaction.set(doc, data);
});
Basically I have a tables collection and each table has an auto generated number like #1, #2, #3
When creating new tables, I want to fetch the latest table number and create the new table with that number incremented by 1.
I wanted to wrap it in a transaction so that if a table created while running the transaction, it will restart so that I don't end up with duplicate numbers.
However, this errors out on the .get(), and from googling I've read that Firestore can't monitor a whole collection within transactions, but instead it requires a specific doc passed to it. Which I obviously can't do because I need to monitor for new docs created in that collection, not changes in a particular doc.
If so, what's the correct way to implement this?
The Firestore transaction API for client apps requires that you get() each individual document that you want to participate in the transaction. So, if you have a query whose results you want to transact with, you will need to:
Perform the query (outside of the transaction)
Collect document references for each document in the result set
In the transaction, get() them all individually.
You will be limited to 500 documents per transaction.
If you want to dynamically look for new documents to modify, you will probably much better off implementing that on the backend using a Firestore trigger in Cloud Functions to automatically handle each new document as they are created, without requiring any code on the client.
Because you're updating just one document, you probably don't need to use transactions for incrementing values.
You can use Firestore Increment to achieve this.
Here is an example taken from here:
const db = firebase.firestore();
const increment = firebase.firestore.FieldValue.increment(1);
// Document reference
const storyRef = db.collection('stories').doc('hello-world');
// Update read count
storyRef.update({ reads: increment });
This is the easiest way to increment values in Firestore.
The goal is to update a Firebase document.
The document ID is unknown at the time of updating. What is known is a property that acts as unique key on the document (email address in the case below).
Based on the official documentation and this answer, the code below works as a method for updating an array within the document.
This feels clunky, though.
Are there more direct methods for retrieving a reference to a single document and updating its fields?
// Set query.
let query = firebase.firestore().collection('users').where('emailAddress', '==', 'test#test.com');
// Run query.
try {
const querySnapshot = await query.get();
return querySnapshot.docs[0].ref.update({
designs: firebase.firestore.FieldValue.arrayUnion('foobar')
})
} catch(e) {
console.log('Error getting user: ', e);
}
No, what you're doing is the best you can do. If you don't know the ID, you have to query to find it. Firestore has no equivalent of a SQL "update where" command.
I am trying to create a list of all documents contained in my main root collection.
I have data like this:
collection -> doc -> collection -> doc
Apologies if posts - posts is confusing!
And this is the data at root level:
I was thinking something like this would work:
const [posts, setPosts] = useState();
useEffect(() => {
const db = firebase.firestore();
return db.collection('posts').onSnapshot((snapshot) => {
const postData = [];
snapshot.forEach((doc) => postData.push({ ...doc.data(), id: doc.id }));
setPosts(postData);
});
}, []);
console.log(posts);
but this just logs an empty array when it should be an array of 2 objects.
Can anyone shine some light on where i'm going wrong? And if i will be able to access the nested data from doing this?
Update after your comment and the screenshot addition
We can see that the documents in the parent posts collection are displayed with an italic font in the Firebase console: this is because these documents are only present (in the console) as "container" of one or more sub-collection but they are not "genuine" documents.
As matter of fact, if you create a document directly under a col1 collection with the full path doc1/subCol1/subDoc1, no intermediate documents will be created (i.e. no doc1 document).
The Firebase console shows this kind of "container" (or "placeholder") in italics in order to "materialize" the hierarchy and allow you to navigate to the subDoc1 document but doc1 document doesn't exist in the Firestore database.
Let's take an example: Imagine a doc1 document under the col1 collection
col1/doc1/
and another one subDoc1 under the subCol1 (sub-)collection
col1/doc1/subCol1/subDoc1
Actually, from a technical perspective, they are not at all relating to each other. They just share a part of their path but nothing else. One side effect of this is that if you delete a document, its sub-collection(s) still exist.
So, it is normal that you get an empty array when doing db.collection('posts').onSnapshot((snapshot) => {...}), because you are listening to un-existing documents.
What you could do is to listen for the documents in one of the posts subcollection, as follows:
db.collection('posts').doc('frlGy...').collection('posts').onSnapshot((snapshot) => {...});
Another possibility would be to use a Collection Group query, to get all the elements in ALL the posts subcollections. But in this case you cannot have the parent and subcollections sharing the same name (i.e. posts).
Update following your second comment
Will I need to do it differently if I, for example, want to be able to
log all posts from all Users, as the Users documents technically wont
exist?
It all depends if you have some user documents or not, which is not clear.
If you do, for each of those user documents, create a posts subcollection, and use db.collection('Users').doc(user.uid).collection('posts').onSnapshot((snapshot) => {...});
You can also use a Collection Group query to query across the different posts subcollections.
If you don't have user documents, you can have a global posts collection (root collection) and you save the user uid as a field of each post document. This way you can secure them (with Security rules) and also query all posts by user. You can also easily query posts across all users.
You need to put the console.log within the callback, as follows:
const [posts, setPosts] = useState();
useEffect(() => {
const db = firebase.firestore();
return db.collection('posts').onSnapshot((snapshot) => {
const postData = [];
snapshot.forEach((doc) => postData.push({ ...doc.data(), id: doc.id }));
console.log(postData); // <------
setPosts(postData);
});
}, []);
Also note that this will only return the frlGy... document, not the one in the subcollection: as said above, Firestore queries are shallow. If you want to query the subcollection, you need to execute an additional query.
const getMarkers= async ()=> {
await db.collection('movies').get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
setlist(doc.data().title);
});
});
}
Its so simple just add async and await on a function and call the function in useeffect