Closing a useEffect Memory Leak in React-Native

When navigating from one set of maps in my expo app to another, I often get this error if I navigate before the maps are done loading:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function

How do I plug this memory leak?

Below is my code:

function MapsScreen({navigation}) { 

const [user_latitude, setUserLatitude] = useState(0)
const [user_longitude, setUserLongitude] = useState(0)
const [position_error, setPositionError] = useState(null)
   
useEffect(() => {
  navigator.geolocation.getCurrentPosition(position => { 
    setUserLatitude(position.coords.latitude);
    setUserLongitude(position.coords.longitude);
    setPositionError(null);
  }, 

  error => setPositionError(error.message),
  {enableHighAccuracy: true, timeout: 20000, maximumAge: 2000}
  ); 
});

2 answers

  • answered 2020-11-24 18:55 Waheed Akhtar

    Reason: You will get this warning when your component is unmounted from the memory and some asyn task is in the process like you make a network call and tries to leave the screen before resolving the promise.

    Solution: To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

    A solution to your problem: As looking at your code you are changing your state when the component tries to unmount, You have to use the cleanup function with the useEffect hook, add a flag before setUserLatitude, setUserLongitude, setPositionError to make sure that the component is not unmounted from the memory

    function MapsScreen({navigation}) { 
    
    const [user_latitude, setUserLatitude] = useState(0)
    const [user_longitude, setUserLongitude] = useState(0)
    const [position_error, setPositionError] = useState(null)
       
    useEffect(() => {
      let mounted = true // Add this 
      navigator.geolocation.getCurrentPosition(position => { 
       if(mounted){ // Add this
        setUserLatitude(position.coords.latitude);
        setUserLongitude(position.coords.longitude);
        setPositionError(null);
        }
      }, 
    
      error => setPositionError(error.message),
      {enableHighAccuracy: true, timeout: 20000, maximumAge: 2000}
      ); 
    
     return () => {
      mounted = false // add this
     }
    }, []);
    

    Read more about it Clean up function

  • answered 2020-11-25 00:51 Handi

    I agree with Waheed answer, btw it is more easier if you already have react-navigation v5.x in your code , it's simple just use useFocusEffect method, the function executed when screen is focused only. I give example with the addition of second check using isActive flag checker.

    import React, { useEffect, useState } from 'react'
    import { useFocusEffect } from '@react-navigation/native'
    
    function MapsScreen({ navigation }) {
      const [user_latitude, setUserLatitude] = useState(0)
      const [user_longitude, setUserLongitude] = useState(0)
      const [position_error, setPositionError] = useState(null)
    
      useFocusEffect(
        React.useCallback(() => {
          let isActive = true
    
          const fetchGeoPositon = () => {
            navigator.geolocation.getCurrentPosition(
              position => {
                if (isActive) {
                  setUserLatitude(position.coords.latitude)
                  setUserLongitude(position.coords.longitude)
                  setPositionError(null)
                }
              },
    
              error => isActive && setPositionError(error.message),
              { enableHighAccuracy: true, timeout: 20000, maximumAge: 2000 }
            )
          }
    
          fetchGeoPositon()
    
          return () => {
            isActive = false
          }
        }, [])
      )
    }
    

    https://reactnavigation.org/docs/use-focus-effect/