31

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

Soviut
  • 88,194
  • 49
  • 192
  • 260
honor
  • 7,378
  • 10
  • 48
  • 76

6 Answers6

50

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, { ... });
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • 1
    By `db.collectionRef` you mean `db.collection`, right? – 1man Jan 29 '20 at 01:55
  • @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? – Przemo Feb 26 '20 at 15:55
  • Ok, As Can I see, it's not working with angularfire :/ – Przemo Feb 26 '20 at 16:49
  • 2
    @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? – Louis Coulet Sep 22 '20 at 12:29
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.

honor
  • 7,378
  • 10
  • 48
  • 76
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, { ... });
Stefan
  • 111
  • 4
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

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;
Eggon
  • 2,032
  • 2
  • 19
  • 38
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);
 }