denislexic
denislexic

Reputation: 11342

firebase onSnapshot gets update before create is complete

I have a "post" that listens to changes on its comments in react like so:

// React hook state 
const [comments, setComments] = useState([])

// My listener in useEffect
db.collection(`users/${userId}/posts/${postId}/comments`)
   .onSnapshot((querySnapshot) => {
      let newComments = []
      querySnapshot.forEach(function (doc) {
        newComments.push({
            id: doc.id,
            ...doc.data()
        })
      })
      setComments(newComments)
   })

When the user creates a new comments, I set a loading state and disable the comment section

// React hook
const [isLoading, setLoading] = useState(false)
// Add comment
const addComment = () => {
   const comment = {text:"hello"}
   setSaving(true)
   db.collection(`users/${postUid}/posts/${postId}/comments`).doc()
      .set(comment)
      .then(()=>{
         setSaving(false)
      })
}

My problem is (a good problem to have), the subscription onSnapshot gets the new comment before my addComment callback is completed, creating some visual issues: - Makes the app look buggy when the comment input is still loading but the comment already there - If there is an error (ex: database permission issue), the comment shows up in the list and then disappears...

Any idea what I can change to not have the onSnapshot update before the create is done?

Upvotes: 1

Views: 606

Answers (1)

Renaud Tarnec
Renaud Tarnec

Reputation: 83058

As explained here in the doc:

Local writes in your app will invoke snapshot listeners immediately. This is because of an important feature called "latency compensation." When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.

Retrieved documents have a metadata.hasPendingWrites property that indicates whether the document has local changes that haven't been written to the backend yet.

See also the following remark in the "Listen to multiple documents in a collection" section:

As explained above under Events for local changes, you will receive events immediately for your local writes. Your listener can use the metadata.hasPendingWrites field on each document to determine whether the document has local changes that have not yet been written to the backend.

So you can use this property to display the change only if it has been written to the back-end, something along the following lines (untested):

db.collection(`users/${userId}/posts/${postId}/comments`)
   .onSnapshot((querySnapshot) => {
      let newComments = []
      querySnapshot.forEach(function (doc) {
        if (!doc.metadata.hasPendingWrites) {
           newComments.push({
               id: doc.id,
               ...doc.data()
           })
        }
      })
      setComments(newComments)
   })

Upvotes: 1

Related Questions