Skip to content
Advertisement

React Native memory leak error after updating to Firebase version 9 onValue

I’m going through to update my code for the Firebase version 9 modular form. I am now using onValue. From what I’m reading it returns a function that removes the listener. But I’m still not doing it right because although it functions well at first, when I change the database on the backend with the app open I get the “can’t perform a react state update on an unmounted component” error when I’m in a different app screen. See old and new code below please.

OLD CODE:

useEffect(() => {
  loadListings();
},[]);

const loadListings = async () => {  
  setLoading(true);
  updateInput('');
  let testData = [];
  let searchData = [];
  db.ref('deals').once('value', (snapshot) =>{
    snapshot.forEach((child)=>{
      testData.push({
        id: child.key,
        title: child.val().hasOwnProperty('title') ? child.val().title : 'NA',
      })
      searchData.push(
        child.val().title
      )
    }) 
  })
  .then(()=>{
    checkMessages(testData);
    setLoading(false);
  })
  .catch((error) => Sentry.Native.captureException('Error MessagesScreen function loadListings 1 ' + error));
}

NEW CODE:

useEffect(() => {
    loadListings();
  },[]);

const loadListings = async () => {  
  setLoading(true);
  updateInput(''); 
  const dbRef = ref(db, 'deals');
  return onValue(dbRef , (snapshot) => {
    let testData = [];
    let searchData = [];
    let storeData = filterStores;
    snapshot.forEach((childSnapshot)=>{
      testData.push({
        id: childSnapshot.key,
        title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
      })
    }) 
    checkMessages(testData);
    setLoading(false);
  })
} 

After receiving answer below I changed the useEffect to this instead and now it works:

  useFocusEffect(
    React.useCallback( () => {
      async function fetchData() {
        // You can await here
        const response = await loadListings();
        // ...
        return () => response();
      }
      fetchData();
    }, [])
  );

Advertisement

Answer

You mentioned the unsubscribe function returned from onValue. In order to call it, I think you’ll want to grab it from the invocation and then call it on some navigation state change.

Assuming you’re using React Navigation, it might look something like this (using the useFocusEffect

import { useFocusEffect } from '@react-navigation/native';

function YourComponent() {
  const [loading, setLoading] = React.useState(false)

  useFocusEffect(
    React.useCallback(async () => {
      const unsubscribe = await loadListings();

      return () => unsubscribe();
    }, [])
  );

  const loadListings = async () => {  
    setLoading(true);
    updateInput(''); 
    const dbRef = ref(db, 'deals');
    return onValue(dbRef , (snapshot) => {
      let testData = [];
      let searchData = [];
      let storeData = filterStores;
      snapshot.forEach((childSnapshot)=>{
        testData.push({
          id: childSnapshot.key,
          title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
        })
      }) 
      checkMessages(testData);
      setLoading(false);
    })
  } 

  return <View />;
}

Also don’t forget to either use async/await for your asynchronous loadListings function, or use .then(). Otherwise you’ll be working with an unresolved promise.

I also found a related StackOverflow question that helped me get to this answer. Maybe that’ll be of some use to you.

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement