nandorojo / swr-firestore

Implement Vercel's useSWR for querying Firestore in React/React Native/Expo apps. πŸ‘©β€πŸš’πŸ”₯
MIT License
777 stars 65 forks source link

Non-listening hooks can cancel other listeners #135

Open jckw opened 3 years ago

jckw commented 3 years ago

Thanks again for this library, it's been a great help in getting firebase to behave in our application.

I came across a bug related to having multiple useCollection hooks on the same collection.

The summary is: it appears that if one hook listens (i.e. passes { listen: true }) and another doesn't, but is called later, then the the first listener breaks.

I've put together a demo repo here that illustrates the problem: https://github.com/jckw/swr-firestore-listen-bug

It seems that we either need to commit to listening, or not, for a particular collection otherwise we risk having listening and non-listening hook calls which can lead to unexpected results.

const Main = () => {
  const { data, add } = useCollection<Note>("notes", { listen: true })

  const [newNote, setNewNote] = useState("")
  const submitNewNote = () => {
    console.log("Adding", newNote)
    add({ content: newNote })
    setNewNote("")

    /**
     * Notes on the effect: since the NonListenList doesn't listen to the collection,
     * neither will update when we trigger this. Interestingly, if you make a change and
     * the code updates with live reloading, then the listener works again.
     */
  }

  return (
    <div>
      <div>swr-firestore listening bug demo</div>

      <hr />
      <p>Listening</p>
      <div>
        {data?.map((n, i) => (
          <div key={i}>{n.content}</div>
        ))}
      </div>
      <hr />
      <p>Non-listening</p>
      <NonListenList />
      <hr />

      <div>
        <form
          onSubmit={(e) => {
            e.preventDefault()
            submitNewNote()
          }}
        >
          <input
            type="text"
            value={newNote}
            onChange={(e) => setNewNote(e.target.value)}
          />
          <button type="submit">Add note</button>
        </form>
      </div>
    </div>
  )
}
const NonListenList = () => {
  const { data } = useCollection<Note>("notes")
  // Adding a listener here makes the other listener work too
  // const { data } = useCollection<Note>("notes", { listen: true })

  return (
    <div>
      {data?.map((n, i) => (
        <div key={i}>{n.content}</div>
      ))}
    </div>
  )
}

export default NonListenList