Home  >  Q&A  >  body text

Choosing a React Hook suitable for use with Firestore’s onsnapshot

<p>I use Firestore snapshots a lot in my React Native app. I also used React hooks. The code looks like this: </p> <pre class="brush:php;toolbar:false;">useEffect(() => { someFirestoreAPICall().onSnapshot(snapshot => { // When the component initially loads, add all loaded data to the state. // When the data on Firestore changes, we receive updates in this callback, // Then update the UI based on the current state });; }, []);</pre> <p>At first, I thought <code>useState</code> was the best hook to store and update the UI. However, according to the settings of my <code>useEffect</code> hook, which comes with an empty dependency array, when the snapshot callback is fired and I try to modify the current state with new changes, the current state is undefined. I think this is due to closures. I was able to solve this problem by using <code>useRef</code> and <code>forceUpdate()</code> with the following code: </p> <pre class="brush:php;toolbar:false;">const dataRef = useRef(initialData); const [, updateState] = React.useState(); const forceUpdate = useCallback(() => updateState({}), []); useEffect(() => { someFirestoreAPICall().onSnapshot(snapshot => { // If snapshot data is added dataRef.current.push(newData) forceUpdate() // If the snapshot data is updated dataRef.current.find(e => some condition) = updatedData forceUpdate() });; }, []); return( // JSX using dataRef.current )</pre> <p>My question is, am I using <code>useRef</code> correctly as well as <code>forceUpdate</code> which is different from <code>useState</code>? I feel like updating the <code>useRef</code> hook and calling <code>forceUpdate()</code> throughout the application is not correct. When trying to use <code>useState</code>, I tried adding the state variable to the dependency array, but it resulted in an infinite loop. I only want to initialize the snapshot function once, and want the stateful data in the component to update over time as the backend changes (triggered in the onSnapshot callback). </p>
P粉555682718P粉555682718398 days ago467

reply all(2)I'll reply

  • P粉806834059

    P粉8068340592023-08-25 12:43:07

    It will be better if you combine useEffect and useState. useEffect is responsible for setting and disabling the listener, and useState is only responsible for the data you need.

    const [data, setData] = useState([]);
    
    useEffect(() => { 
           const unsubscribe = someFirestoreAPICall().onSnapshot(snap => {
             const data = snap.docs.map(doc => doc.data())
             this.setData(data)
           });
    
           //记得在卸载组件时取消实时监听器,否则会造成内存泄漏
           return () => unsubscribe()
    }, []);

    You can then directly reference "data" in the useState hook in your application.

    reply
    0
  • P粉165522886

    P粉1655228862023-08-25 12:37:51

    A simple useEffect worked for me, i don't need to create a helper function or anything of sorts,

    useEffect(() => {
            const colRef = collection(db, "data")
            //real time update
            onSnapshot(colRef, (snapshot) => {
                snapshot.docs.forEach((doc) => {
                    setTestData((prev) => [...prev, doc.data()])
                    // console.log("onsnapshot", doc.data());
                })
            })
        }, [])

    A simple useEffect works for me, I don't need to create any helper functions or anything like that,

    useEffect(() => {
            const colRef = collection(db, "data")
            //实时更新
            onSnapshot(colRef, (snapshot) => {
                snapshot.docs.forEach((doc) => {
                    setTestData((prev) => [...prev, doc.data()])
                    // console.log("onsnapshot", doc.data());
                })
            })
        }, [])

    reply
    0
  • Cancelreply