Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 773
BOOnZ Avatar asked Apr 23 '18 01:04

BOOnZ


2 Answers

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:

  • Events for local changes
  • The hasPendingWrites metadata property
  • Snapshot Listen Options
like image 168
grohjy Avatar answered Oct 29 '22 01:10

grohjy


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.

like image 2
David East Avatar answered Oct 29 '22 00:10

David East