2

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'?

Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
MartinJ
  • 333
  • 2
  • 13

2 Answers2

1

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.

Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
  • 1
    Yeehaa! Works a treat. Silly error on my part. Many thanks for putting me straight. Your [Firestore: What's the pattern for adding new data in Web v9?](https://stackoverflow.com/questions/68987326/firestore-whats-the-pattern-for-adding-new-data-in-web-v9) reference is very helpful too - I'm relieved to see that I'm not the only one getting confused here! – MartinJ Feb 21 '22 at 18:28
  • You're welcome! Glad my answers were useful :) – Dharmaraj Feb 21 '22 at 18:29
  • @MartinJ if you are saying `doc(db, 'transactionLogs')` and not specifying the doc ID in `doc()` then this'll end up throwing an error. You need `doc(collection(db, 'test'))` to add doc with random ID. – Dharmaraj Feb 22 '22 at 09:55
1

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

MartinJ
  • 333
  • 2
  • 13