I am trying to update a user's firestore doc from a Firebase Function using a query, and having issues getting it to work. My Function code is the following:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
/**
* A webhook handler function for the relevant Stripe events.
*/
// Here would be the function that calls updatePlan and passes it the customer email,
// I've omitted it to simplify the snippet
const updatePlan = async (customerEmail) => {
await admin.firestore()
.collection('users').where('email', '==', customerEmail).get()
.then((doc) => {
const ref = doc.ref;
ref.update({ 'purchasedTemplateOne': true });
});
};
I'm getting the following error in the firebase logs when the query is run:
Exception from a finished function: TypeError: Cannot read properties of undefined (reading 'update')
Any help regarding what I may be doing wrong or suggestions on how I could achieve this would be greatly appreciated, Thank you in advance!
Update:
I was able to solve my problem with more understanding of Firestore Queries:
const updatePlan = (customerEmail) => {
const customerQuery = admin.firestore().collection("users").where("email", "==", customerEmail)
customerQuery.get().then(querySnapshot => {
if (!querySnapshot.empty) {
// Get just the one customer/user document
const snapshot = querySnapshot.docs[0]
// Reference of customer/user doc
const documentRef = snapshot.ref
documentRef.update({ 'purchasedTemplateOne': true })
functions.logger.log("User Document Updated:", documentRef);
}
else {
functions.logger.log("User Document Does Not Exist");
}
})
};
The error message is telling you that doc.ref is undefined. There is no property ref on the object doc.
This is probably because you misunderstand the object that results from a Firestore query. Even if you are expecting a single document, a filtered query can return zero or more documents. Those documents are always represented in an object of type QuerySnapshot. That's what doc actually is - a QuerySnapshot - so you need to treat it as such.
Perhaps you should check the size of the result set before you access the docs array to see what's returned by the query. This is covered in the documentation.
Related
I have a firebase firestore with some reference datatype.
It looks like this:
I Hope, this is the correct way.
Now when i get my user from firebase, i have this reference object with the id of it.
But if i call my function to get the doc, i get an error message:
First my function and how i call it:
export const getClubById = async (id: string) => {
const doc = collection(db, 'clubs', id)
return doc
}
const userData = dbUser.data()
const club = await getClubById(userData.selectedClub.id)
console.log('club', club)
And here the error message:
Uncaught (in promise) FirebaseError: Invalid collection reference. Collection references must have an odd number of segments, but clubs/vA7R94pX3bpHDsYIr6Ge has 2.
If you have the DocumentReference already then you can use getDoc() function to retrieve the document from Firestore as shown below:
import { getDoc, DocumentReference } from "firebase/firestore";
export const getClubById = async (clubDocRef: DocumentReference) => {
const clubSnapshot = await getDoc(clubDocRef);
return clubSnapshot.data();
}
// Pass the reference itself to the function instead of doc ID
const club = await getClubById(userData.selectedClub)
For the error in the question, to create a DocumentReference, if you have the document ID then you should doc() function instead of collection() that is used to create a CollectionReferencce as shown below:
const docRef = doc(db, 'clubs', clubID);
Also checkout: Firestore: What's the pattern for adding new data in Web v9?
I have a collection where it contains a couple of docs which all contains a id-field individual. But i can't figuere out how I can delete a specific document based on my query.
I have tried with this:
const deleteItem = async(item) => {
const d = query(collection(db, 'allTasks'), where('id', '==', item.id));
const docSnap = await getDocs(d);
docSnap.forEach((doc) => {
console.log(doc.data())
deleteDoc(doc.data());
});
}
But i get the error: [Unhandled promise rejection: TypeError: t is not an Object. (evaluating '"_delegate" in t')]
Is this the wrong way or should i use batch?
The console.log shows the right item that i have clicked delete on
The deleteDoc() function take DocumentReference as parameter and not the document data.
docSnap.forEach((doc) => {
deleteDoc(doc.ref); // and not doc.data()
});
Additionally, it might be a good idea to use a batch to delete documents to ensure they are all delete or none.
I was using the chaining mode of the Firestore Web 8, but I'm in the way of updated it to Module 9 and have been a hard time trying to figure out how to get all the content of my subcollection (collection inside my collection).
My older function is like this and works fine:
function getInfo(doc_name) {
let infoDB = db
.collection("collection_name")
.doc(doc_name)
.collection("subcollection_name")
.get();
return alunoHistorico;
}
so with the module way I tried this code
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const docRef = doc(db, "collection_name", "doc_name");
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!");
}
but the function doc() expects a even arguments (not counting the db argument) so if I try to use with 3 arguments like this, I get a error:
const docRef = doc(db, "collection_name", "doc_name", "subcollection_name");
to it work I have to pass the exactly document that is inside the subcollection
const docRef = doc(db, "collection_name", "doc_name", "subcollection_name", "sub_doc");
but it doesn't work for me because I have a list os docs inside the subcollection, that I want o retrieve.
So how can I get all my docs inside my subcollection?
Thanks to anyone who take the time.
You need to use collection() to get a CollectionReference instead of doc() which returns a DocumentReference:
const subColRef = collection(db, "collection_name", "doc_name", "subcollection_name");
// odd number of path segments to get a CollectionReference
// equivalent to:
// .collection("collection_name/doc_name/subcollection_name") in v8
// use getDocs() instead of getDoc() to fetch the collection
const qSnap = getDocs(subColRef)
console.log(qSnap.docs.map(d => ({id: d.id, ...d.data()})))
I wrote a detailed answer on difference between doc() and collection() (in V8 and V9) here:
Firestore: What's the pattern for adding new data in Web v9?
If someone want to get realtime updates of docs inside sub collection using onSnapshot in Modular Firebase V9, you can achieve this like:
import { db } from "./firebase";
import { onSnapshot, collection } from "#firebase/firestore";
let collectionRef = collection(db, "main_collection_id", "doc_id", "sub_collection_id");
onSnapshot(collectionRef, (querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log("Id: ", doc.id, "Data: ", doc.data());
});
});
I have a problem that's bugging me for days. I am trying to create a Firebase Cloud function that reads from the Firestore database.
My Firestore DB looks like this:
Problem is that I cannot list users like this:
db.collection('users').get().then((snapshot) => snapshot.forEach(...));
If I try to do this I get empty response, like there are no users in my users collection.
But I try to access user directly it works:
await db.collection('users/5CZxgu8nmNXu2TgplwOUdOIt8e33/receipts').get()
My complete code:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.cat = functions.https.onRequest(async (req, res) => {
const receiptList: any = [];
const db: Firestore = admin.firestore();
const usersRef = await db.collection('users').get();
console.log(usersRef.empty); // Returns true
const receiptsRef = await db
.collection('users/5CZxgu8nmNXu2TgplwOUdOIt8e33/receipts')
.get();
receiptsRef.forEach((receipt: any) => {
console.log(receipt);
receiptList.push(receipt);
// Here I can access data
});
res.send(receiptList);
return '';
});
Does anyone have any idea what I'm doing wrong? Thank you!
Your users collection is actually empty. See how the document IDs are shown in italics? That means there is not actually a document in its place, however, there are subcollections with documents organized underneath them.
When you query a collection, you only get the documents that are immediately within that collection. A query will not pick up documents organized in subcollections. In this respect, queries are said to be "shallow". As you've seen, you need to reach deeper into the subcollection to get its documents.
Bottom line is that the queries you're showing are doing exactly what they're supposed to do.
Thanks again Doug for your help.
I manage to solve my problem. Here is my complete solution.
import * as functions from 'firebase-functions';
import {
Firestore
} from '#google-cloud/firestore';
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.cat = functions.https.onRequest(async (req, res) => {
const receiptList: any = [];
const db: Firestore = admin.firestore();
const receipts = await db.collectionGroup('receipts').get();
receipts.forEach((doc: any) => {
console.log(doc.id, ' => ', doc.data());
receiptList.push(doc.data());
});
res.send(receiptList);
return '';
});
.get() gets all documents. In your case those documents are empty therefore .get() doesn't consider them.
The simplest solution that I found for this is to replace .get() with .listDocuments(). Now you could read each doc entry like you would a doc.
I wrote a very simple Cloud Function to add some fields in Firestore with some info from the newly created FirebaseUser in FirebaseAuth.
In Firestore, I have a collection named "highscore". Everytime, a new user is created, I want to add a document with the firebaseusers uid as document, and 2 fields, like:
highscore/uid/score & highscore/uid/usernick (e.g highscore/fgt38gudg9/430 & highscore/fgt38gudg9/cooldude45)
This is my function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.database()
//On user creation, trigger: Add information to /highscore/uid
exports.onUserCreation = functions.auth.user().onCreate((user) => {
const collection = db.collection("highscore")
const userid = user.uid
const usernick = user.displayName
collection.doc(userid).set({
score: 0
user: usernick
})
})
However, when the function is triggered, I run into this error:
TypeError: db.collection is not a function
at exports.onUserCreation.functions.auth.user.onCreate (/srv/index.js:11:24)
at cloudFunctionNewSignature (/srv/node_modules/firebase-functions/lib/cloud-functions.js:120:23)
at /worker/worker.js:825:24
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
I can't figure this out. Any suggestions?
I think you are using Firstore:
const db = admin.firestore()
admin.database() gets you a reference to the Realtime Database instance for your project. What you want to use instead is admin.firestore().
Also, you will want to return the promise that you get from set(), otherwise, the operation might not complete before the function terminates.
return collection.doc(userid).set({
score: 0
user: usernick
})
Be sure to read the documentation about terminating functions to understand your obligations in dealing with async work represented by promises.