最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - clearInterval() not working in React Native functional component - Stack Overflow

programmeradmin0浏览0评论

I have a screen ponent 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 prehension.

Here is the relevant code for the ponent:

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?

I have a screen ponent 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 prehension.

Here is the relevant code for the ponent:

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?

Share Improve this question edited Apr 26, 2020 at 0:22 SuperHanz98 asked Apr 25, 2020 at 23:59 SuperHanz98SuperHanz98 2,2402 gold badges19 silver badges35 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 7

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);
  }
};

ponentWillUnmount is use for cleanup (like removing event listeners, cancel the timer etc). Say you are adding a event listener in ponentDidMount and removing it in ponentWillUnmount as below.

ponentDidMount() {
  window.addEventListener('mousemove', () => {})
}

ponentWillUnmount() {
  window.removeEventListener('mousemove', () => {})
}

Hook equivalent of above code will be as follows

useEffect(() => {
  window.addEventListener('mousemove', () => {});

  // returned function will be called on ponent unmount 
  return () => {
    window.removeEventListener('mousemove', () => {})
  }
}, [])

So your better code is:

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

Don't store something like an interval id in state, as re-renders occur each update. If you're functional, implement the setInterval with useRef(), if class based, use this.interval.

Another gotcha is calling clearInterval() in a functional ponent on the ref, instead of .current

heres a snippet from what i just debugged:

  const spinnerCount = useRef(0)
  const interval = useRef(null)

  useEffect(() => {
    if (withProgress && inProgress && notification == '') {
      interval.current = setInterval(() => {
        if (spinnerCount.current >= 40) {
          clearInterval(interval.current)
          spinnerCount.current = 0
          setNotification('Something happened... Please try again.')
        } else {
          spinnerCount.current = spinnerCount.current + 1
        }
      }, 1000)
    } 
    if (notification !== '' && inProgress === false) {
      const delay = notification.length > 100 ? 6000 : 3000
      setTimeout(() => {
        clearInterval(interval.current)
        spinnerCount.current = 0
        setNotification('');
      }, delay);
    }
  }, [inProgress])

Theres a little extra in there, basically this is a disappearing notification ponent, that also features a progress spinner. in this case, if the ponent is displaying the spinner, but a success/error notification is never triggered, the spinner will auto-quit after 40 seconds. hence the interval/spinnerCount

发布评论

评论列表(0)

  1. 暂无评论