Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

clearInterval() not working in React Native functional component

I have a screen component that has a getPosition() function called every second by an interval.

If the stopRace() function is called or if the user presses the physical/graphical back button, I want to clear this interval so it doesn't continue to run in the background.

To do this, I've tried to store the interval ID in the raceUpdateInterval state variable.

I then clear this interval using clearInterval(raceUpdateInterval) in the stopRace() function and the cleanup() function.

When I call the stopRace() function, then press back, the interval is cleared. I know this because my console logs:

Still Running
Still Running
Still Running
Reached cleanup function

However, if I press the back button, the interval does not clear. Instead my console logs:

Still Running
Still Running
Still Running
Reached cleanup function
Still Running

Followed by a memory leak warning containing the following advice:

To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function

Which is exactly what I'm trying to do, but is not working for some reason beyond my comprehension.

Here is the relevant code for the component:

const RaceScreen = ({route, navigation}) => {

    const [raceUpdateInterval, setRaceUpdateInterval] = useState(0);

    useEffect(function() {
        return function cleanup() {
            console.log('Reached cleanup function')
            clearInterval(raceUpdateInterval)
        }
      }, []);

    function getPosition(){
        console.log("Still being called")
        //get position
    }

    function startRace(){
        setRaceUpdateInterval(setInterval(getPosition, 1000))
    }

    function stopRace(){
        clearInterval(raceUpdateInterval)
    }

Why does the stopRace() function correctly clear the interval but the cleanup() function doesn't?

like image 636
SuperHanz98 Avatar asked Jan 25 '23 02:01

SuperHanz98


1 Answers

Part of the reason your code might not have been working as it was, is that if you ran the startRace function more than once without stopping it in between, the interval would start up again but the interval ID would've been lost.

The main reason it failed to clear is that the raceUpdateInterval that it saw at the beginning when the useEffect with [] as a dependency array saw was: 0. The reason why it didn't see the updated values is because useEffect creates a closure over the values at the point in which it runs (and re-runs). So you'd need to use a reference to give it access to the latest version of the raceUpdateInterval

Here's how I would modify your code to get it working properly. Instead of starting the timer in a function, use the useEffect to start up that side effect that way there will never be an instance where the timer fails to clean up.

I added the function to the interval using a ref because I don't know how many closure variables there are in the getPosition function. This way the positionFunctRef.current always points to the latest version of the function rather than remaining static.

const RaceScreen = ({ route, navigation }) => {
  const [runningTimer, setRunningTimer] = useState(false);
  function getPosition() {
    console.log('Still being called');
    //get position
  }
  const positionFunctRef = useRef(getPosition);
  useEffect(() => {
    positionFunctRef.current = positionFunctRef;
  });

  useEffect(
    function () {
      if (!runningTimer) {
        return;
      }

      const intervalId = setInterval(() => {
        positionFunctRef.current();
      }, 1000);
      return () => {
        console.log('Reached cleanup function');
        clearInterval(intervalId);
      };
    },
    [runningTimer]
  );

  function startRace() {
    setRunningTimer(true);
  }

  function stopRace() {
    setRunningTimer(false);
  }
};
like image 193
Zachary Haber Avatar answered Feb 07 '23 19:02

Zachary Haber