Damien Romito
Damien Romito

Reputation: 10055

React UseEffect and Unsubscribe promise with conditional listener ! (Optimize Firestore onsnapshot)

I never find a solution for my useEffect probleme : I'm useing firebase and create a listener (onSnapshot) on my database to get the last state of my object "Player" I can get when the states currentGroup and currentUser are available

const [currentGroup, setCurrentGroup] = useState(null)
const [currentUser, setCurrentUser] = useState(null)
const [currentPlayer, setCurrentPlayer] = useState(null)

const IDefineMyListener = () =>{

    return firebase.doc(`group/${currentGroup.id}${users/${currentUser.id}/`)
       .onSnpashot(snap =>{
            //I get my snap because it changed
            setCurrentPlayer(snap.data())
       }) 
}

Juste above, i call a useEffect when the currentGroup and currentUser are available and (IMPORTANT) if I didn't already set the currentPlayer

 useEffect(() => {

   if (!currentGroup || !currentUser)  return

   if (!currentPlayer) {
     let unsubscribe = IDefineMyListener()
     return (() => {
        unsubscribe() 
     })
   }

},[currentGroup,currentUser])

As you can think, unsubscribe() is called even if the IDefineMyListener() is not redefined. In other words, when currentGroup or currentUser changes, this useEffect deleted my listener whereas I NEED IT.

How can i figure out ?!

PS :if I remove if (!currentPlayer), of course it works but will unlessly get my data

PS2 : If I remove the unsubscribe, my listener is called twice each time.

Upvotes: 0

Views: 2525

Answers (2)

Damien Romito
Damien Romito

Reputation: 10055

The problem was about my bad understanding of the unsubscribe() . I didn't return anything in my useEffect, but save my unsusbcribe function to call it when i need.

let unsubscribe = null //I will save the unsubscription listener inside it
const myComponent = () => {

  const [currentGroup, setCurrentGroup] = useState(null)
  const [currentUser, setCurrentUser] = useState(null)
  const [currentPlayer, setCurrentPlayer] = useState(null)

  useEffect(() => {
    if (!currentGroup || !currentUser)  {
      if(unsubscribe){
        unsubscribe() // 2 - when I dont need of my listener, I call the unsubscription function
      }
      return
    }

    if (!currentPlayer && !unsubscribe) {
      unsubscribe = IDefineMyListener()  // 1 -  I create my listener and save the unsubscription in a persistant variable
    }
  },[currentGroup,currentUser])

  const IDefineMyListener = () =>{
      return firebase.doc(`group/${currentGroup.id}${users/${currentUser.id}/`)
        .onSnpashot(snap =>{
              //I get my snap because it changed
              setCurrentPlayer(snap.data())
        }) 
  }

  ...

Upvotes: 1

Utsav Patel
Utsav Patel

Reputation: 2889

You can use useCallback hook to work around this.

First we'll define your listener using useCallback and give the dependency array the arguments as currentGroup and currentUser.

const IDefineMyListener = useCallback(event => {

   return firebase.doc(`group/${currentGroup.id}${users/${currentUser.id}/`)
   .onSnpashot(snap =>{
        //I get my snap because it changed
        setCurrentPlayer(snap.data())
   })

}, [currentGroup, currentUser]); 

And we will only use useEffect to register and deregister your listener.

useEffect(() => {   

   //subscribe  the listener
   IDefineMyListener()

     return (() => {
        //unsubscribe the listener here
        unsubscribe() 
     })
   }

},[])

Since we passed an [] to useEffect, it will only run once when the component is mounted. But we have already registered the callback. So your callback will run everytime the currentGroup or currentUser changes without deregistering your listener.

Upvotes: 5

Related Questions