How do you create a new Firestore document within a transaction - javascript

I'm trying to log changes to a collection of customer records. In order to keep things watertight,the log records should obviously be created within a Firestore transaction. I have no problems using transaction.set to apply the customer document changes, but every such change needs to be accompanied by the creation of a new document within my transactionLogs collection. Here, things are going badly wrong The log documents are identified by a timestamp field and when I run the following code: ...
import { initializeApp } from 'firebase/app';
import {
getFirestore, collection, query, getDoc,
getDocs, where, orderBy, addDoc, doc, updateDoc, deleteDoc, runTransaction
} from 'firebase/firestore';
var firebaseApp = initializeApp(firebaseConfig);
var db = getFirestore(firebaseApp);
// code to read and validate update to customer documents and to call the following asynch function
function performCustomerUpdate(parameters) {
await runTransaction(db, async (transaction) => {
// update Customer document and build a transactionLog object
const newLogRef = collection(db, 'transactionLogs', transactionLog.timeStamp);
await transaction.set(newLogRef, transactionLog);
});
}
... the transaction.set instruction fails saying something like "Invalid collection reference. Collection references must have an odd number of segments, but transactionsLogs/1645451217221 has 2." In this particular instance, 1645451217221 would have been the value of transactionLog.timesStamp.
Does anyone have advice on what is going on here and how to fix the error? My understanding is that transaction.set will create a new record if the supplied reference doesn't exist, so I ought to be on the right lines. But why does Firestore think that I want to create it in a collection called transactionsLogs/1645451217221? How do I get it the create a reference for a document identified by the string '1645451217221' in a collection called 'transactionsLogs'?

If you are specifying the document ID (and not using the auto-generated IDs) then you must use doc() instead of collection() to create a DocumentReference:
const newLogRef = doc(db, 'transactionLogs', transactionLog.timeStamp);
The collection() function is used create a CollectionReference.
Also checkout: Firestore: What's the pattern for adding new data in Web v9?
My understanding is that transaction.set will create a new record if the supplied reference doesn't exist
If the documents exists, the it'll overwrite the existing document.

With regard to the wider question of how you add records within a transaction, the answer is that you would use pretty much the same code as you'd use outside a transaction.
So whereas outside a transaction you would create a new document with a data item as its identifier with the following Web Version 9 code:
const myDocRef = doc(db, "myCollection", myDocId);
await setDoc(myDocRef, myDocData);
Inside a transaction, you use exactly the same pattern with the one exception that setDoc() is replaced by transaction.set() and the whole operation (obviously) is surrounded by a runTransaction(db, async (transaction) => { instruction:
await runTransaction(db, async (transaction) => {
const myDocRef = doc(db, "myCollection", myDocId);
await transaction.set(myDocRef, myDocData);
}).catch((error) => {
alert(`Oops - transaction failed - error is : ${error}`);
});
Similarly, the pattern used to create a document with an automatically-generated id:
const myCollectionRef = collection(db, "myCollection");
const myDocRef = doc(myCollectionRef)
await setDoc(myDocRef, myDocData);
is replaced within a transaction by
const myCollectionRef = collection(db, "myCollection");
const myDocRef = doc(myCollectionRef)
await transaction.set(myDocRef, myDocData);
Note that if you find you need to find the value that's be assigned to your automatically-generated id, this is available as myDocRef.id

Related

Firestore - Store a property id is reference type by firestore Batch transaction

I trying to store data content reference type by batch transaction, then I got an exception:
Function WriteBatch.set() called with invalid data. Unsupported field value: a custom object (found in document orders/OC9dZErupEhPsamp8QEd)
Is there a way we can use batch transaction to store reference type?
this is my code:
batch.update(orderRef, {
userId: firestore.doc(userId),
});
Normaly update() use to update existing firestore data. Review firestore docs for the same. In that given example they are updating population by increments of value or with new population number but before passing it in each update function values are stored in one cost value if it is not static value. as Asked by #dharmaraj please edit your questions by posting with full code you can also read given firestore documentation for your own studies.
import firebase from "firebase/app";
const app = firebase.initializeApp({});
const firestore = app.firestore();
const batch = firestore.batch();
const newUserId = firestore.doc(userId);
batch.update(orderRef, {
userId: newUserId,
});
Log newUserId value and see what are you getting into it.
You can't store the reference object that doc() returns, it's an object that may have circular references and functions in it. doc() is not the id of the document. If you want to get the id (which is a string), then:
const newUserId = firestore.doc(userId).ref.id;
batch.update(orderRef, {
userId: newUserId,
});
I don't know why batch validate input should be a pure object. I tried to push reference type id inside nested object then it work well, yeah I know it already is a trick, but it work.
change:
batch.update(docRef, {
user: firestore.collection('users').doc(userId)
})
to:
batch.update(docRef, {
user: {
id: firestore.collection('users').doc(userId)
}
})

Firebase JS9 access subcollection with DocumentSnapshot

Just quick question.
How to do something like this in firebase javascript v9? userDoc is a DocumentSnapshot .
const postRef = userDoc.ref.collection('posts').doc(slug);
I need to access sub-collection with doc snapshot, but I am totally clueless.
You can pass the document reference to the collection function, and the collection ref that gives to the doc function:
const postRef = doc(collection(userDoc.ref, 'posts', slug));
Alternatively, you can skip the collection call here as both collection and doc accept multiple arguments:
const postRef = doc(userDoc.ref, 'posts', slug);

How to get access from one collection to another collection in firebase

how to get access from one collection to another collection in firebase in JS v9
Firebase's JS API v9 brought different changes.
One of the biggest changes is the fact that the DocumentReference don't allow the access to subcollections anymore. Or at least, not directly from the DocumentReference itself, how we used to to with v8.
In v8, for example, we could do something like this:
//say we have a document reference
const myDocument = db.collection("posts").doc(MY_DOC_ID);
//we can access the subcollection from the document reference and,
//for example, do something with all the documents in the subcollection
myDocument.collection("comments").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// DO SOMETHING
});
});
With v9, we have a different approach. Let's say we get our document:
const myDocument = doc(db, "posts", MY_DOC_ID);
As you can note, the way we write the code is different. In v8 we used to write it in a procedural way. With v9, everything switched to a more functional way, where we can use functions such as doc(), collection() and so on.
So, in order to do the same thing we did with the above example and do something with every doc in the subcollection, the code for v9 API should look like this:
const subcollectionSnapshot = await getDocs(collection(db, "posts", MY_DOC_ID, "comments"));
subcollectionSnapshot.forEach((doc) => {
// DO SOMETHING
});
Note that we can pass additional parameters to functions such as collection() and doc(). The first one will always be the reference to the database, the second one will be the root collection and from there onward, every other parameter will be added to the path. In my example, where I wrote
collection(db, "posts", MY_DOC_ID, "comments")
it means
go in the "posts" collection
pick the document with id equals to MY_DOC_ID
go in the "comments" subcollection of that document

How to enable persistence on reactfire?

I'd like to implement Firestore offline persistence on my PWA React app using the reactfire library.
const firestore = useFirestore().enablePersistence();
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
but running the code i get an error:
FirebaseError: Firestore has already been started and persistence can no longer be enabled. You can only enable persistence before calling any other methods on a Firestore object.
This component is wrapped inside a <Suspense> as mentioned in the documentation
That database read is the only one that i make in the entire app, how can i solve?
Edit.
Using the example that #Ajordat gave, I've imported the preloadFirestore function inside the App component I do get an error:
"Cannot read property 'name' of undefined".
Whereas adapting (because I cannot use hooks inside the fetch function)
the example from #DougStevenson: I've imported useFirestore function in the App component (in order to get the Firestore object) to enable persistence, and then importing it (useFirestore) into my component in order to retrieve the data, but now, I get the same error as before,
Firestore has already been started and persistence can no longer be enabled.
Edit 2:
I've tried to enablePersistence without errors, thank guys, this is my approach, let me know if it is the best:
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
And in my custom component:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let document = useFirestoreDocDataOnce(docRef);
console.log(document)
But now I do have a problem, when I log the document, the data are not emitted instantly, yeah I know that it is an asynchronous operation, but the component is wrapped inside a <Suspense>, in this way:
<Suspense fallback={<div>Loading</div>}>
<FoodComponent foodName={"Milkshake"} />
</Suspense>
But I don't see the loading text before the component is actually rendered.
Does the suspense fragment show the fallback component only while is loading the function (useFirestore) and not the actual data?
Well, I've solved, have to destructure the data, doing like that:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document)
On other JavaScript libraries for Firestore, enablePersistence() returns a promise. That means it will complete some time in the future, with no guarantees how long it will take. If you're executing the query immediately after you call enablePersistence(), without waiting for the returned promise to become fulfilled, then you will see this error message. That's because the query "beats" the persistence layer and effectively executes first.
You will have to figure out how to use that promise to wait until it's OK to make that query with persistence enabled. For example:
seFirestore().enablePersistence()
.then(() => {
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
})
.catch(error => {
console.error("enablePersistence failed", error);
})
Notice how the query will complete only after the persistence is fully enabled.
Thanks for the suggestion guys #DougStevenson and #Ajordat
In app component:
import { useFirestore } from "reactfire"
...
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
In your custom component, where you want to use Firestore:
import { useFirestore, useFirestoreDocData /* or what you want to use */ } from "reactfire"
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document);

Cloud functions cron job: How to manage the nested collection's data

I want to trigger the scheduled task to the nested collection data using cloud functions cron job.
What I want to achieve is, if the timestamp value in "limit" field is later than the current time, "status" field will be added with the value "expired" in the nested collection.
The parent collection name is "OrdersUser" and the child collection name is "Orders".
And I want to manage the documents in "Orders" collection.
The scheduled task runs every one minute.
After I deployed, I got an error in the firebase console.
id is not defined.
I thought I could use wild card in the cloud functions, so I used {id} to refer the nested documents. But I couldn't.
I want to query all the data in ”Orders” collection.
How can I fix this issue?
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp()
const db = admin.firestore()
const ref = functions.firestore
exports.statusOrdersUser = functions.runWith( { memory: '2GB' }).pubsub.schedule('* * * * *').onRun((context) => {
// Consistent timestamp
const now = admin.firestore.Timestamp.now();
// Query all documents ready to perform
//id is not defined.
const queryOrdersUser = db.collection('OrdersUser').doc({id}).collection('Orders').where('limit', '<=', now)
return queryOrdersUser.get()
.then(querySnapshot => {
if (querySnapshot.empty) {
return null;
} else {
const promises = []
querySnapshot.forEach(doc => {
promises.push(doc.ref.update({ status: 'expired' }));
});
return Promise.all(promises);
}
});
})
Firestore does not support wildcard operations for document IDs. If you want to query a subcollection, you need to provide the specific ID of the document where it is nested. It's not clear how your function is supposed to know exactly which subcollection to query, since it doesn't receive any arguments or have any context.
If you want to query all of the subcollections named "Order", no matter where they are nested anywhere in your database, you can use a collection group query for that.

Categories

Resources