firebase firestore adding new document inside a transaction - transaction.add is not a function
Asked Answered
S

6

32

I was assuming that it was possible to do something like:

transaction.add(collectionRef,{
  uid: userId,
  name: name,
  fsTimestamp: firebase.firestore.Timestamp.now(),
});

But apparently it is not:

transaction.add is not a function

The above message is displayed inside the chrome console.

I see that we can use the set method of the transaction to add a new document transactionally. see: https://firebase.google.com/docs/firestore/manage-data/transactions

The thing is if I use set instead of add(which is not supported anyways), the id of the document should be created by me manually, firestore won't create it. see: https://firebase.google.com/docs/firestore/manage-data/add-data

Do you see any downside of this not having an add method that generates the id for you automatically?

For example, is it possible that the id generated by the firestore itself is somehow optimized considering various concerns including performance?

Which library/method do you use to create your document IDs in react-native while using transaction.set?

Thanks

Sofer answered 14/4, 2019 at 10:6 Comment(0)
G
55

If you want to generate a unique ID for later use in creating a document in a transaction, all you have to do is use CollectionReference.doc() with no parameters to generate a DocumentReference which you can set() later in a transaction.

(What you're proposing in your answer is way more work for the same effect.)

// Create a reference to a document that doesn't exist yet, it has a random id
const newDocRef = db.collection('coll').doc();

// Then, later in a transaction:
transaction.set(newDocRef, { ... });
Goldthread answered 14/4, 2019 at 17:16 Comment(4)
By db.collectionRef you mean db.collection, right?Arbor
@DougStevenson hmm, when I try this I have an error: FirebaseError: Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: undefined. There were any updates?Ossian
Ok, As Can I see, it's not working with angularfire :/Ossian
@DougStevenson: does it mean that the doc id is then reserved? If so: is it needed to call doc() within the transaction? Or do we rely on the id generator to not statistically provide this value in another unrelated call?Omnifarious
S
6

after some more digging I found in the source code of the firestore itself the below class/method for id generation:

export class AutoId {
  static newId(): string {
    // Alphanumeric characters
    const chars =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let autoId = '';
    for (let i = 0; i < 20; i++) {
      autoId += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
    return autoId;
  }
}

see: https://github.com/firebase/firebase-js-sdk/blob/73a586c92afe3f39a844b2be86086fddb6877bb7/packages/firestore/src/util/misc.ts#L36

I extracted the method (except the assert statement) and put it inside a method in my code. Then I used the set method of the transaction as below:

generateFirestoreId(){
        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        let autoId = '';
        for (let i = 0; i < 20; i++) {
            autoId += chars.charAt(Math.floor(Math.random() * chars.length));
        }
        //assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
        return autoId;
    }

then,

newDocRef = db.collection("PARENTCOLL").doc(PARENTDOCID).collection('SUBCOLL').doc(this.generateFirestoreId());
                        transaction.set(newDocRef,{
                            uid: userId,
                            name: name,
                            fsTimestamp: firebase.firestore.Timestamp.now(),
                        });

Since I am using the same algo for the id generation as the firestore itself I feel better.

Hope this helps/guides someone.

Cheers.

Sofer answered 14/4, 2019 at 10:42 Comment(0)
I
1

Based on the answer from Doug Stevenson, this is how I got it worked with @angular/fire:

// Create a reference to a document and provide it a random id, e.g. by using uuidv4
const newDocRef = this.db.collection('coll').doc(uuidv4()).ref;

// In the transaction:
transaction.set(newDocRef, { ... });
Investigate answered 7/6, 2020 at 14:8 Comment(0)
L
0

To complete Stefan's answer. For those using Angularfire, earlier to version 5.2 using CollectionReference.doc() results in an error "CollectionReference.doc() requires its first argument to be of type non-empty string".

This workaround worked for me:

const id = this.afs.createId();
const ref = this.afs.collection(this.collectionRef).doc(id);
transaction.set(ref, { ... });

Credit: https://github.com/angular/angularfire/issues/1974#issuecomment-448449448

Lysias answered 14/9, 2020 at 13:21 Comment(0)
G
0

I'd like to add an answer solving the id problem. There's no need to generate your own ids. The documentReference is updated after the transaction.set() is called, so in order to access the Firestore's id you need to just do the following:

const docRef = collectionRef.doc();
const result = await transaction.set(docRef, input);
const id = docRef.id;
Gore answered 21/9, 2021 at 10:18 Comment(0)
M
0

First of all, firestore transaction object has 4 (get,set,update,delete) methods and doesnt has "add" method. However, the "set" method can be used instead.

import { collection,doc,runTransaction } from "firebase/firestore";

On the other hand documentReference must be created for "set" method.

Steps :

1-) collection method create a collectionReference object.

const collectionRef = collection(FirebaseDb,"[colpath]");

2-) doc method create a documentReference object with unique random id for specified collectionReference.

const documentRef = doc(collectionRef);

3-) add operation can be performed with the transaction set method

try {
      await runTransaction(FirebaseDb,async (transaction) => {
                    

             await transaction.set(documentRef, {
                      uid: userId,
                      name: name,
                      fsTimestamp: firebase.firestore.Timestamp.now(),
                    });
   
        })

 } catch (e) {
   console.error("Error : ", e);
 }
Marston answered 29/8, 2022 at 12:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.