Skip to content
Advertisement

return a cleanup function returning form the async function in userEffect in react Native

I am using a firebase onSnapshot method to use realtime data in react native app and wrote a separate async function to listen to the firestore as given below,

async function getData() {

    let collName = await AsyncStorage.getItem('@collection_name')
    let subscriber = shopReference.collection(collName).onSnapshot((snap) => {
        let menuList = [];
        snap.forEach((doc) => {
          menuList.push({
            id: doc.id,
            ...doc.data(),
          });
        });
    
        setMenu(menuList);
      });

      return subscriber;
}

I am using useEffect to call this function and I want to return the subscriber method returning from the firestore as a cleanup function. I know that I can achieve that by directly implement this code inside the useEffect function. But since I am using AsyncStorage to retrieve a collection id I need to put it inside async function. I can’t return the response of the getData() method since it returns promise instead of subscriber function. What is the best solution to resolve this issue?

EDIT

I tried all the following methods to call the function inside the useEffect function.

// Without returning anything, but according to the firestore doc. This may cause data leaks.
useEffect(() => {
        getData();
      }, []);

// directly returning the method, this cause error that promise cant be accepted as a clean up function.
useEffect(() => {
        return getData();
      }, []);



useEffect(() => {
        getData().then((subscriber) => {
             return () => subscriber();
        });
      }, []);

Advertisement

Answer

You cannot change getData to immediately return a cleanup function. You’d rather use

useEffect(() => {
    let subscriber = () => {};
    getData().then(sub => {
         subscriber = sub;
    });
    return () => {
         subscriber();
    };
}, []);

or

useEffect(() => {
    const subscriberPromise = getData();
    return async () => {
         const subscriber = await subscriberPromise;
         subscriber();
    };
}, []);

However, these are not ideal when the cleanup function gets called before the promise was fulfilled (not to mention error handling…). While the first doesn’t work at all in that case, the second still unnecessarily starts the subscription. To do it properly, I’d suggest something like

useEffect(() => {
    let cleanup = () => {};
    AsyncStorage.getItem('@collection_name').then(collName => {
        if (!cleanup) return; // already cancelled
//      ^^^^^^^^^^^^^^^^^^^^
        cleanup = shopReference.collection(collName).onSnapshot((snap) => {
            let menuList = [];
            snap.forEach((doc) => {
                menuList.push({
                    id: doc.id,
                    ...doc.data(),
                });
            });
            setMenu(menuList);
        });
    });

    return () => {
         cleanup();
         cleanup = null;
    };
}, []);
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement