I have these collection of items from firestore:
availability : true
stocks: 100
item: item1
I kind of wanted to decrement the stocks after submitting the form: I have these where() to compare if what the user chose is the same item from the one saved in the firestore.
function incrementCounter(collref) {
collref = firestore
.collection("items")
.doc()
.where(selectedItem, "==", selectedItem);
collref.update({
stocks: firestore.FieldValue.increment(-1),
});
}
This is how I'll submit my form and I've set the incrementCounter() after saving it:
const handleSubmit = (e) => {
e.preventDefault();
try {
const userRef = firestore.collection("users").doc(id);
const ref = userRef.set(
{
....
},
},
{ merge: true }
);
console.log(" saved");
incrementCounter();
} catch (err) {
console.log(err);
}
};
There's no error in submitting the form. However, the incrementCounter() is not working and displays this error:
TypeError: _Firebase_utils__WEBPACK_IMPORTED_MODULE_5__.firestore.collection(...).doc(...).where is not a function
There are few problems here
There should be async-await for both functions
Your fieldValue should start from firebase.firestore.FieldValue not firestoreFieldValue
Also where clause is used for collection, not doc() so remove that as well. Also I don't think this will update the full collection but do check it and see. (The error you are getting is because of this)
I don't know how you are importing firebase in this application and I don't know how you have declared firestore but mostly firestore variable is declared like this
const firestore = firebase.firestore();
In here firestore is a function, not a property
But when you are using it in the FieldValue then it should be like
firebase.firestore.FieldValue.increment(-1),
Notice that firestore here is a property not a function
Your full code should be like this
async function incrementCounter(collref) {
collref = firestore
.collection("items")
.where(selectedItem, "==", selectedItem);
const newRef = await collref.get();
for(let i in newRef.docs){
const doc = newRef.docs[i];
await doc.update({
stocks: firebase.firestore.FieldValue.increment(-1),
});
// You can also batch this
}
}
const handleSubmit = async (e) => {
e.preventDefault();
try {
const userRef = firestore.collection("users").doc(id);
const ref = await userRef.set(
{
....
},
},
{ merge: true }
);
console.log(" saved");
await incrementCounter();
} catch (err) {
console.log(err);
}
};
The where() method exists on a CollectionReference and not a DocumentReference. You also need to get references to those documents first so first get all the matching documents and then update all of them using Promise.all() or Batch Writes:
function incrementCounter() {
// not param required ^^
const collref = firestore
.collection("items")
// .doc() <-- remove this
.where(selectedItem, "==", selectedItem);
// ^^^ ^^^
// doc field field value
// "item" {selectedItemName}
collRef.get().then(async (qSnap) => {
const updates = []
qSnap.docs.forEach((doc) => {
updates.push(doc.ref.update({ stocks: firebase.firestore.FieldValue.increment(-1) }))
})
await Promise.all(updates)
})
}
If you are updating less than 500 documents, consider using batch writes to make sure all updates either fail or pass:
collRef.get().then(async (qSnap) => {
const batch = firestore.batch()
qSnap.docs.forEach((doc) => {
batch.update(doc.ref, { stocks: firebase.firestore.FieldValue.increment(-1) })
})
await batch.commit()
})
You can read more about batch writes in the documentation
I'm creating an expense tracker app where:
(Task completed) The user can set a monthly budget (ie. $3,000) that is stored in a '--currentBudget--' document in a year-month collection.
(Task completed) The user can add an expense through a form (title, amount, category, date) which is stored in a unique document in the year-month collection, the data is then aggregated in the '--totalSpent--' by total spending and categorized total spending.
(Not completed) The user can delete an expense that would update the values in the '--totalSpent--'. How would I do this using Cloud Firestore? I'm thinking I could reference the document I'm deleting and decrement by the amount value to its respected year-month(date) record based on total spending and categorized total spending.
Firestore Console
https://i.stack.imgur.com/eKTUg.jpg
/* ADD EXPENSE */
function addExpense(user) {
// DOM Form Selectors
const expenseForm = document.getElementById('expense-form');
const expenseTitle = document.getElementById('expense-title');
const expenseAmount = document.getElementById('expense-amount');
const expenseCategory = document.getElementById('expense-category');
const expenseDate = document.getElementById('expense-date');
// Submit Event
expenseForm.addEventListener('submit', e => {
e.preventDefault();
// Database Reference
const increment = firebase.firestore.FieldValue.increment(expenseAmount.value);
const expenseRef = db.collection('users').doc(user.uid).collection(expenseDate.value).doc();
const totalSpent = db.collection('users').doc(user.uid).collection(expenseDate.value).doc('--totalSpent--');
// Database Batch Write
const batch = db.batch();
// Creates Unqiue Expense Document
batch.set(expenseRef, { title: expenseTitle.value, amount: expenseAmount.value, category: expenseCategory.value, date: expenseDate.value });
// Updates totalSpending
batch.set(totalSpent, { totalSpending: increment }, { merge: true });
//Updates totalSpending by category
batch.set(totalSpent, { [expenseCategory.value]: increment }, { merge: true });
batch.commit();
// Reset Form
expenseForm.reset();
modal[1].classList.add('hidden');
overlay.classList.add('hidden');
});
}
That is a perfect fit for a Firebase Cloud Functions solution.
You can use the onCreate and onDelete event triggers to change the total value. With the usage of transactions you ensure a safe execution.
If the users can even change the values in existing expenses you could also use the onUpdate trigger to handle even those use cases.
Here is a basic example for your database and for the onCreate and onDelete triggers:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
exports = module.exports = functions.firestore
.document("/users/{userUid}/{year_month}/{expenseUid}")
.onCreate(async (eventSnapshot, context) => {
const { userUid, expenseUid } = context.params;
const expenseData = eventSnapshot.data();
const { spending = 0 } = expenseData;
try {
await admin.firestore().runTransaction(async (t) => {
const totalRef = admin
.firestore()
.doc("/users/{userUid}/{year_month}/--totalSpend--");
const doc = await t.get(totalRef);
const newTotalSpending = doc.data().totalSpending || 0 + spending;
t.update(totalRef, { totalSpending: newTotalSpending });
});
console.log("Transaction success!");
} catch (e) {
console.log("Transaction failure:", e);
}
});
exports = module.exports = functions.firestore
.document("/users/{userUid}/{year_month}/{expenseUid}")
.onDelete(async (eventSnapshot, context) => {
const { userUid, expenseUid } = context.params;
const expenseData = eventSnapshot.data();
const { spending = 0 } = expenseData;
try {
await admin.firestore().runTransaction(async (t) => {
const totalRef = admin
.firestore()
.doc("/users/{userUid}/{year_month}/--totalSpend--");
const doc = await t.get(totalRef);
const newTotalSpending = doc.data().totalSpending || 0 - spending;
t.update(totalRef, { totalSpending: newTotalSpending });
});
console.log("Transaction success!");
} catch (e) {
console.log("Transaction failure:", e);
}
});
The main benefit of using the cloud functions is that you don't need to give your users the right to edit the total value and all changes are done automaticaly in teh backend.
I have a cloud function that has the following code. And I call for query from my iOS app. Even though the data is in the Cloud Firestore collection, the function still go to the else statement meaning console print "NOT IN COLLECTION". can someone help?
cloud function code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
const FieldValue = admin.firestore.FieldValue;
exports.validateShop = functions.https.onCall((data, context) => {
const uid = context.auth.uid;
console.log("Function called by UID: " + uid);
const email = context.auth.token.email;
console.log("Email: " + email);
const shop = data.shop;
console.log("Recieved: "+ data);
const docRef = db.collection("Users").where("shopName", "==", shop);
docRef.get().then(function(doc) {
if (doc.exists) {
const docDelete = db.collection("shops").doc(uid);
const updateDoc = docDelete.doc(uid).update({
"shopName": FieldValue.delete(),
});
console.log(shop + ": EXISTS. DOCUMENT UPDATED ");
return {success: true};
} else {
console.log(shop + ": NOT IN COLLECTION ");
return {success: false};
}
}).catch((error) => {
return {"shop": "Error getting document"};
});
return {
message: shop,
};
});
And this is how I call it from my iOS app:
func validateTurn(){
let data = ["shop": "ThaiSook"]
functions.httpsCallable("validateShop").call(data) { (result, error) in
print("Function returned")
if let err = error {print(err)}
if let res = result {print(res)}
}
}
If there is no document at the location referenced by docRef, the resulting document will be empty and calling exists on it will return false.
Aside of double checking the existence of the document you are trying to get, I recommend you to read the example of getting a document from Firestore with Node.js [1].
You may solve your issue by using in your code the keyword await [2].
[1] https://firebase.google.com/docs/firestore/query-data/get-data#get_a_document
[2] https://javascript.info/async-await
This is the code where I add data to firestore.
How can I check if idEveniment field already exist in collection?
And if exist I dont want to add the new collection.
Maybe return an error ?
import { firestore } from 'firebase';
// Add Eveniment
export const addEveniment = data => async(dispatch, getState, {getFirestore}) => {
const firestore = getFirestore();
const userId = getState().firebase.auth.uid;
dispatch({type: actions.ADD_EVENIMENT_START});
try {
const res = await firestore.collection('evenimente').doc(userId).get();
const newEveniment = {
idEveniment: data.idEveniment,
eveniment: data.eveniment,
}
if(!res.data()) {
firestore.collection('evenimente').doc(userId).set({
evenimente: [newEveniment],
});
} else {
firestore.collection('evenimente').doc(userId).update({
evenimente: [...res.data().evenimente, newEveniment],
});
}
dispatch({type: actions.ADD_EVENIMENT_SUCCESS});
return true;
} catch(err) {
dispatch({type: actions.ADD_EVENIMENT_FAIL, payload: err.message})
}
}
In your current data structure you can't check for documents with a specific value of idEveniment. You can use array-contains to check array membership but only if you specify the combinarion of idEveniment and eveniment.
If you want to be able to check just on the value of idEveniment, consider adding a field idEveniments to each document with just that value. Then you can use array-contains for the existing of a specific idEveniment value in the idEveniments array:
const evenimentesRef = firebase.firestore().collection("evenimente");
const query = evenimentesRef.where("idEveniments", "array-contains", "376342").limit(1);
query.get().then((snapshot) => {
if (!snapshot.empty()) {
... a document with the value already exists
}
});
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id);
jobskill_ref.delete();
Error thrown
jobskill_ref.delete is not a function
You can only delete a document once you have a DocumentReference to it. To get that you must first execute the query, then loop over the QuerySnapshot and finally delete each DocumentSnapshot based on its ref.
var jobskill_query = db.collection('job_skills').where('job_id','==',post.job_id);
jobskill_query.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.delete();
});
});
I use batched writes for this. For example:
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id);
let batch = firestore.batch();
jobskill_ref
.get()
.then(snapshot => {
snapshot.docs.forEach(doc => {
batch.delete(doc.ref);
});
return batch.commit();
})
ES6 async/await:
const jobskills = await store
.collection('job_skills')
.where('job_id', '==', post.job_id)
.get();
const batch = store.batch();
jobskills.forEach(doc => {
batch.delete(doc.ref);
});
await batch.commit();
//The following code will find and delete the document from firestore
const doc = await this.noteRef.where('userId', '==', userId).get();
doc.forEach(element => {
element.ref.delete();
console.log(`deleted: ${element.id}`);
});
the key part of Frank's answer that fixed my issues was the .ref in doc.ref.delete()
I originally only had doc.delete() which gave a "not a function" error. now my code looks like this and works perfectly:
let fs = firebase.firestore();
let collectionRef = fs.collection(<your collection here>);
collectionRef.where("name", "==", name)
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
doc.ref.delete().then(() => {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
or try this, but you must have the id beforehand
export const deleteDocument = (id) => {
return (dispatch) => {
firebase.firestore()
.collection("contracts")
.doc(id)
.delete()
}
}
You can now do this:
db.collection("cities").doc("DC").delete().then(function() {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
And of course, you can use await/async:
exports.delete = functions.https.onRequest(async (req, res) => {
try {
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id).get();
jobskill_ref.forEach((doc) => {
doc.ref.delete();
});
} catch (error) {
return res.json({
status: 'error', msg: 'Error while deleting', data: error,
});
}
});
I have no idea why you have to get() them and loop on them, then delete() them, while you can prepare one query with where to delete in one step like any SQL statement, but Google decided to do it like that. so, for now, this is the only option.
If you're using Cloud Firestore on the Client side, you can use a Unique key generator package/module like uuid to generate an ID. Then you set the ID of the document to the ID generated from uuid and store a reference to the ID on the object you're storing in Firestore.
For example:
If you wanted to save a person object to Firestore, first, you'll use uuid to generate an ID for the person, before saving like below.
const uuid = require('uuid')
const person = { name: "Adebola Adeniran", age: 19}
const id = uuid() //generates a unique random ID of type string
const personObjWithId = {person, id}
export const sendToFireStore = async (person) => {
await db.collection("people").doc(id).set(personObjWithId);
};
// To delete, get the ID you've stored with the object and call // the following firestore query
export const deleteFromFireStore = async (id) => {
await db.collection("people").doc(id).delete();
};
Hope this helps anyone using firestore on the Client side.
The way I resolved this is by giving each document a uniqueID, querying on that field, getting the documentID of the returned document, and using that in the delete. Like so:
(Swift)
func rejectFriendRequest(request: Request) {
DispatchQueue.global().async {
self.db.collection("requests")
.whereField("uniqueID", isEqualTo: request.uniqueID)
.getDocuments { querySnapshot, error in
if let e = error {
print("There was an error fetching that document: \(e)")
} else {
self.db.collection("requests")
.document(querySnapshot!.documents.first!.documentID)
.delete() { err in
if let e = err {
print("There was an error deleting that document: \(e)")
} else {
print("Document successfully deleted!")
}
}
}
}
}
}
The code could be cleaned up a bit, but this is the solution I came up with. Hope it can help someone in the future!
const firestoreCollection = db.collection('job_skills')
var docIds = (await firestoreCollection.where("folderId", "==", folderId).get()).docs.map((doc => doc.id))
// for single result
await firestoreCollection.doc(docIds[0]).delete()
// for multiple result
await Promise.all(
docIds.map(
async(docId) => await firestoreCollection.doc(docId).delete()
)
)
delete(seccion: string, subseccion: string)
{
const deletlist = this.db.collection('seccionesclass', ref => ref.where('seccion', '==', seccion).where('subseccion', '==' , subseccion))
deletlist.get().subscribe(delitems => delitems.forEach( doc=> doc.ref.delete()));
alert('record erased');
}
The code for Kotlin, including failure listeners (both for the query and for the delete of each document):
fun deleteJobs(jobId: String) {
db.collection("jobs").whereEqualTo("job_id", jobId).get()
.addOnSuccessListener { documentSnapshots ->
for (documentSnapshot in documentSnapshots)
documentSnapshot.reference.delete().addOnFailureListener { e ->
Log.e(TAG, "deleteJobs: failed to delete document ${documentSnapshot.reference.id}", e)
}
}.addOnFailureListener { e ->
Log.e(TAG, "deleteJobs: query failed", e)
}
}