BOOnZ
BOOnZ

Reputation: 848

firestore onSnapshot executing twice

My firestore onSnapshot() function is being called twice.

let user = firebase.firestore().collection('users').doc(userID).onSnapshot
({                    
    next: (documentSnapshot: firebase.firestore.DocumentSnapshot) =>
    {
         this.userArray.push(documentSnapshot as User);
         console.log(documentSnapshot);
         //here
    },
    error: (firestoreError: firebase.firestore.FirestoreError) =>
    {
         console.log(firestoreError);
         //here
    }
});

I have also tried subscribing like in https://firebase.google.com/docs/firestore/query-data/listen#detach_a_listener by including user() at the //here comment but to no avail.

How can I modify such that the function only executes one time, i.e. push only one user object per time instead of twice.

Upvotes: 12

Views: 7870

Answers (2)

grohjy
grohjy

Reputation: 2149

I don't know if this is related to your question. If one is using

firebase.firestore.FieldValue.serverTimestamp()

to give a document a timestamp, then onSnaphot will fire twice. This seem to be because when you add a new document to your database onSnapshot will fire, but the serverTimestamp has not run yet. After a few milliseconds serverTimestamp will run and update you document => onSnapshot will fire again.

I would like to add a small delay before onSnapshot fires (say 0,5s or so), but I couldn't find the way to do this.

You can also make a server side function for onCreate event, I believe that would solve your problem. Maybe your userArray.push-action would be more suitable to execute in server side.


Update: To learn more about the behavior of serverTimestamp() and why it triggers the listener twice read this article: The secrets of Firestore’s FieldValue.serverTimestamp() — REVEALED!. Also, the official documentation states:

When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.

In the article there are a couple of suggested solutions, one of which is to use the metadata property of the snapshot to find whether the Boolean value of metadata.hasPendingWrites is true (which tells you that the snapshot you’re looking at hasn’t been written to the server yet) or false.

For example, in your case you can check whether hasPendingWrites is false and then push the object:

if ( !documentSnapshot.metadata.hasPendingWrites ){
  // This code will only execute once the data has been written to the server 
  this.userArray.push(documentSnapshot as User);
  console.log(documentSnapshot);
}

In a more generic example, the code will look like this:

firestore.collection("MyCollection")
.onSnapshot( snapshot => {

    if ( snapshot.metadata.hasPendingWrites ){
        // Local changes have not yet been written to the backend
    } else {
        // Changes have been written to the backend
    }

});

Another useful approach, found in the documentation is the following:

If you just want to know when your write has completed, you can listen to the completion callback rather than using hasPendingWrites. In JavaScript, use the Promise returned from your write operation by attaching a .then() callback.

I hope these resources and the various approaches will help anyone trying to figure out a solution.

REFERENCES:

Upvotes: 29

David East
David East

Reputation: 32604

If you need a one time response, use the .get() method for a promise.

firebase.firestore().collection('users').doc(userID).get().then(snap => {
  this.userArray = [...this.userArray, snap.doc);
});

However, I suggest using AngularFire (totally biased since I maintain the library). It makes handling common Angular + Firebase tasks much easier.

Upvotes: 1

Related Questions