I'm trying to get a doc in a collection and if it doesn't exist I create a new one, I would like to know if there is any way to get userData even if it doesn't exist (so to assign it the new one I have created) or I need to take it with
firebase
.firestore()
.collection("users")
.doc(ID);
again
const userRef = firebase
.firestore()
.collection("users")
.doc(ID);
const user = await userRef.get();
if (!user.exists) {
userRef.set(userSchema);
}
const userData = user.data();
I'm not sure where the user data is coming from but let's have a test user as follows:
const userData = {
name: "TestUser",
uid: "1234",
verified: true
}
Then here goes the function you need:
async function addUserToDatabase(userData) {
//Document Reference
const userDocRef = admin.firestore().collection("users").doc(userData.uid)
//Checking if document exists
if ((await userDocRef.get()).exists) {
return "User document already exists!"
}
//Document does not exists so create one
await userDocRef.set(userData)
return
}
Related
I'm trying to do a comparison between user session email and an email in an array in firestore. That is, I want to search for the login email within the database and if any email is found, bring some information to the screen, such as name and surname.
I even managed to get inside the array and make this comparison, but I can't make the "var UserName" leave the { } of the IF
Can someone help me?
my code is:
const [data, setData] = useState([]);
const getUsers = () => {
firestore()
.collection("users")
.get()
.then((querySnapshot) => {
let userFirestore = [];
querySnapshot.forEach((doc) => {
const usuario = {
id: doc.id,
nome: doc.data().nome,
sobrenome: doc.data().sobrenome,
email: doc.data().email,
profissao: doc.data().profissao,
}
userFirestore.push(usuario);
});
userFirestore.forEach(function (item, indice, array) {
if (item.email === user.email){ //user.email brings the email of the logged in user
var nomeUsuario = item.nome
console.log(nomeUsuario) //UserName brings the result I expected
}
});
})
.catch((error) => console.log(error));
}
You can use a query to get a document with current user's email that instead of fetching the whole collection as shown below:
firestore()
.collection("users")
.where("email", "==", user.email)
.get().then((querySnapshot) => {
if (querySnapshot.empty) {
console.log("User not found")
return;
}
const user = querySnapshot.docs[0].data();
console.log(user)
})
Even better, if you use user's UID as the Firestore document ID (highly recommended), then you can get that single document by ID as shown below:
firestore()
.collection("users")
.doc(user.uid) // < user.uid must be defined
.get()
.then((snapshot) => {
console.log(snapshot.data())
})
When you fetch the whole collection, you are charged N read where N is total number of documents in the collection and it'll be slower as you are downloading plenty of data.
I'm currently ripping my hair out trying to query a nested collection in my Firestore db.
my db is set up as follows:
users (collection)
- username
- displayName
- ratedItems (collection)
- itemId
- timestamp
- likedItem
I want to be able to add/update the rated item in the ratedItems collection for a specific user but I seem to be hitting 2 problems:
I assume the query is incorrect as if(querySnapshot.empty) always returns false even if there are no matching items (matching the itemId) in the ratedItems collection, so it always attempts to add a new item which results in duplicate entries in the collection.
if I force the code to bypass the conditional it throws an error when it attempts to update the existing item:
Expected type 'mc', but it was: a custom yc object
My thoughts are I am using the collectionGroup query incorrectly but I haven't found a solution for this yet. Should I even be using collectionGroup at all?? from what I've read, if I understand correctly this will query every ratedItem collection regardless of the user, which isn't what I want
const rateItem = async (itemId, liked) => {
try {
const itemRef = collection(db, 'users', currentUser.uid, 'ratedItems');
const q = query(
collectionGroup(db, 'users', currentUser.uid),
where('itemId', '==', itemId)
);
const querySnapshot = await getDocs(q);
if (querySnapshot.empty) {
await addDoc(itemRef, {
itemId: itemId,
timestamp: serverTimestamp(),
likedItem: liked,
});
} else {
await updateDoc(itemRef, {
timestamp: serverTimestamp(),
likedItem: liked,
});
}
} catch (err) {
console.log(err.message);
}
};
I assume the query is incorrect as if(querySnapshot.empty) always returns false even if there are no matching items (matching the itemId) in the ratedItems collection, so it always attempts to add a new item which results in duplicate entries in the collection.
You used the itemRef for both of addDoc and updateDoc which is not the proper way to do it. You need a correct query syntax in order to update your Firestore document.
if I force the code to bypass the conditional it throws an error when it attempts to update the existing item
You can try the code below to update your nested document:
const updateitemRef = query(
collection(db, 'users', 'user_item', 'ratedItems'),
where('itemId', '==', itemId)
);
const itemSnapshot = await getDocs(updateitemRef);
itemSnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
updateDoc(doc.ref, {
likedItem: liked,
timestamp: serverTimestamp()
});
});
For the complete code, you can try the code below:
const rateItem = async (itemId, liked) => {
try {
const q = query(
collectionGroup(db, 'users', currentUser.uid),
where('itemId', '==', itemId)
);
const querySnapshot = await getDocs(q);
const additemRef = collection(db, 'users', currentUser.uid, 'ratedItems');
const updateitemRef = query(
collection(db, 'users', currentUser.uid, 'ratedItems'),
where('itemId', '==', itemId)
);
const itemSnapshot = await getDocs(updateitemRef);
if (querySnapshot.empty) {
await addDoc(additemRef, {
itemId: itemId,
likedItem: liked,
timestamp: serverTimestamp()
});
} else {
itemSnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
updateDoc(doc.ref, {
likedItem: liked,
timestamp: serverTimestamp()
});
});
}
} catch (err) {
console.log(err.message);
}
};
For more references in creating a query, you can refer to the guides below:
Add a document
Perform simple and compound queries in Cloud Firestore
Heres what i am trying to achieve i want a unique id field for every document in my database and i want that unique id to the same as the document id.
Example:
documents: data:
eBgdTRE123 id:'eBgdTRE123'
name:'Jhon Doe'
job:'Programmer'
i want i databse to have this structure, now i got two ideas to achieve this
1: to use cloud function and have onCreate listener and everytime theres a new document grab document and set the id field and update it heres how i am doing it
exports.addDocIdToField =
functions.firestore.document('collectionname/{docID}').onCreate((snap,context) => {
const id = context.params.docID;
return admin.firestore().collection('collectionname')
.doc(id).set({ id: snap.id }, { merge: true })
.then(() => {
return null;
})
.catch((error) => {
return null;
});
})
2: to use the above kind of method on document creation. add a new document as soon as that document is added get the newly added document and update its id
both of them work but my question is can i rely on this kind of operation? i mean if the id is by any means undefined it can cause an error further in the app.
or if there are other ways to achieve this?
See JS SDK v9 syntax at the bottom
There is a simpler way to achieve that, using the doc() method, as follows (here with the JavaScript SDK v8)
var newDocRef = db.collection('collectionname').doc();
newDocRef.set({
name:'Jhon Doe',
job:'Programmer',
id: newDocRef.id
})
As explained in the doc:
(the doc() method) gets a DocumentReference for the document within the collection at the
specified path. If no path is specified, an automatically-generated
unique ID will be used for the returned DocumentReference.
You will find similar methods in the other Client SDKs, here for Android and here for iOS.
UPDATE FOR JS SDK v9:
import { collection, doc, setDoc } from "firebase/firestore";
const newDocRef = doc(collection(db, "collectionname"));
await setDoc(
newDocRef,
{
name:'Jhon Doe',
job:'Programmer',
id: newDocRef.id
}
)
the previous method works fine but just for the sake of clarification
what is it really like
const { doc, collection, getFirestore, setDoc, addDoc } = require('firebase/firestore');
let collectionId = "Cars";
let docId;
let firestore = getFirestore();
async function addDocWithId() {
let collectionRef = collection(firestore, collectionId)
addDoc(collectionRef, {}).then(res => {
docId = res.id
let docRef = doc(firestore, collectionId + "/" + docId)
setDoc(docRef, {
id: docId,
car: 'Benz'
})
})
};
how it has been clarified
const { doc, collection, getFirestore, setDoc, addDoc } = require('firebase/firestore')
let collectionId = "Cars"
let firestore = getFirestore()
async function addDocWithId() {
let collectionRef = collection(firestore, collectionId)
let docRef = doc(collectionRef)
setDoc(docRef, {
id: docRef.id,
car: "Volvo"
})
}
If in case if there's anyone who didn't have luck with above provided answers, try this -> docref.set({ 'id':docref.ref.id}). It worked for me. Below is a usecase of this.
create(tutorial: any): any {
var docref = this.db.collection('invxxx').doc()
docref.set({ 'id':docref.ref.id, anotherField: 'anotherValue'});
}
Heres what i am trying to achieve i want a unique id field for every document in my database and i want that unique id to the same as the document id.
Example:
documents: data:
eBgdTRE123 id:'eBgdTRE123'
name:'Jhon Doe'
job:'Programmer'
i want i databse to have this structure, now i got two ideas to achieve this
1: to use cloud function and have onCreate listener and everytime theres a new document grab document and set the id field and update it heres how i am doing it
exports.addDocIdToField =
functions.firestore.document('collectionname/{docID}').onCreate((snap,context) => {
const id = context.params.docID;
return admin.firestore().collection('collectionname')
.doc(id).set({ id: snap.id }, { merge: true })
.then(() => {
return null;
})
.catch((error) => {
return null;
});
})
2: to use the above kind of method on document creation. add a new document as soon as that document is added get the newly added document and update its id
both of them work but my question is can i rely on this kind of operation? i mean if the id is by any means undefined it can cause an error further in the app.
or if there are other ways to achieve this?
See JS SDK v9 syntax at the bottom
There is a simpler way to achieve that, using the doc() method, as follows (here with the JavaScript SDK v8)
var newDocRef = db.collection('collectionname').doc();
newDocRef.set({
name:'Jhon Doe',
job:'Programmer',
id: newDocRef.id
})
As explained in the doc:
(the doc() method) gets a DocumentReference for the document within the collection at the
specified path. If no path is specified, an automatically-generated
unique ID will be used for the returned DocumentReference.
You will find similar methods in the other Client SDKs, here for Android and here for iOS.
UPDATE FOR JS SDK v9:
import { collection, doc, setDoc } from "firebase/firestore";
const newDocRef = doc(collection(db, "collectionname"));
await setDoc(
newDocRef,
{
name:'Jhon Doe',
job:'Programmer',
id: newDocRef.id
}
)
the previous method works fine but just for the sake of clarification
what is it really like
const { doc, collection, getFirestore, setDoc, addDoc } = require('firebase/firestore');
let collectionId = "Cars";
let docId;
let firestore = getFirestore();
async function addDocWithId() {
let collectionRef = collection(firestore, collectionId)
addDoc(collectionRef, {}).then(res => {
docId = res.id
let docRef = doc(firestore, collectionId + "/" + docId)
setDoc(docRef, {
id: docId,
car: 'Benz'
})
})
};
how it has been clarified
const { doc, collection, getFirestore, setDoc, addDoc } = require('firebase/firestore')
let collectionId = "Cars"
let firestore = getFirestore()
async function addDocWithId() {
let collectionRef = collection(firestore, collectionId)
let docRef = doc(collectionRef)
setDoc(docRef, {
id: docRef.id,
car: "Volvo"
})
}
If in case if there's anyone who didn't have luck with above provided answers, try this -> docref.set({ 'id':docref.ref.id}). It worked for me. Below is a usecase of this.
create(tutorial: any): any {
var docref = this.db.collection('invxxx').doc()
docref.set({ 'id':docref.ref.id, anotherField: 'anotherValue'});
}
I'm using Firebase scheduled function to periodically check data in Firestore if users' detail are added to SendGrid contact list. After they're successfully added to SendGrid, I want to update the value in firestore addToContact from false to true.
exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async(context) => {
// Query data from firestore
const querySnapshot = await admin.firestore().collection('users')
.where('metadata.addToContact', '==', false)
.get();
// Call SendGrid API to add contact
// If success, change metadata.addToContact to true
if (response) {
const docRef = admin.firestore().collection('users').doc(""+context.params.id);
await docRef.update( {'metadata.sendToSg': true }, { merge: true } );
}
}
I want to access context.params.id but I realise that context that passed in .onRun isn't the same with the context passed in a callable function.
NOTE: If I pass context.params.id to .doc without ""+, I got an error Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string. After google the answer, I tried using ""+context.params.id then I can console.log the context value. I only got
context {
eventId: '<eventID>',
timestamp: '2020-11-21T09:05:38.861Z',
eventType: 'google.pubsub.topic.publish',
params: {}
}
I thought I'd get document ID from params object here but it's empty.
Is there a way to get Document ID from firestore in a scheduled function?
The scheduled Cloud Function itself has no notion of a Firestore document ID since it is triggered by the Google Cloud Scheduler and not by an event occurring on a Firestore document. This is why the corresponding context object is different from the context object of a Firestore triggered Cloud Function.
I understand that you want to update the unique document corresponding to your first query (admin.firestore().collection('users').where('metadata.addToContact', '==', false).get();).
If this understanding is correct, do as follows:
exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async (context) => {
// Query data from firestore
const querySnapshot = await admin.firestore().collection('users')
.where('metadata.addToContact', '==', false)
.get();
// Call SendGrid API to add contact
// If success, change metadata.addToContact to true
if (response) {
const docRef = querySnapshot.docs[0].ref;
await docRef.update({ 'metadata.sendToSg': true }, { merge: true });
} else {
console.log('No response');
}
return null;
});
Update following your comment:
You should not use async/await with forEach(), see here for more details. Use Promise.all() instead, as follows:
exports.addContact = functions.region(region).pubsub.schedule('every 4 minutes').onRun(async (context) => {
// Query data from firestore
const querySnapshot = await admin.firestore().collection('users')
.where('metadata.addToContact', '==', false)
.get();
// Call SendGrid API to add contact
// If success, change metadata.addToContact to true
if (response) {
const promises = [];
querySnapshot.forEach((doc) => {
const { metadata } = doc.data();
if (metadata.sendToSg == false) {
promises.push(doc.ref.update({ 'metadata.sendToSg': true }, { merge: true }));
}
})
await Promise.all(promises);
} else {
console.log('No response');
}
return null;
});