Dariusz Sikorra
Dariusz Sikorra

Reputation: 23

How to set and clean interval by toggle function in react with hooks?

I'm trying to build a timer witch is triggered by one button with function handleToggle, which looks like:

const handleToggle = () => {
    let timer = () => setInterval(() => {
      dispatch({ type: "COUNT", payload: Date.now() - startTime });
    }, 100);
    const startTime = Date.now() - AppState.runningTime;
    if (AppState.countingStarted) {
      dispatch({
        type: "COUNTING_STARTED",
        payload: false
      });
      clearInterval(timer());
    } else {
      timer()
      dispatch({ type: "COUNTING_STARTED", payload: true });
    }
  };

COUNTING_STARTED is a dispatch function which toggles AppState.countingStarted.

I'm struggling with stopping a timer, how to clear that timer-Interval? I tried:

const handleToggle = () => {
    let timer;
    if (AppState.countingStarted) {
      dispatch({
        type: "COUNTING_STARTED",
        payload: false
      });
      clearInterval(timer);
    } else {
      timer = setInterval(() => {
        dispatch({ type: "COUNT", payload: Date.now() - startTime });
      }, 100);
      const startTime = Date.now() - AppState.runningTime;
      dispatch({ type: "COUNTING_STARTED", payload: true });
    }
  };

but there is no difference...

Upvotes: 1

Views: 1126

Answers (2)

Olivier Boissé
Olivier Boissé

Reputation: 18113

In your example, the reference to the intervalId is lost at the end of the function execution.

You should use an instance variable to store the intervalId. If you have a Class component you can use the following code :

handleToggle = () => {
  // clear the interval, no matter the value of AppState.countingStarted
  // this prevent from recreating the interval multiple times
  clearInterval(this.timerId);

  if (AppState.countingStarted) {
     dispatch({type: "COUNTING_STARTED",payload: false});   
  } else {
    // store the timer id in an instance variable
    this.timerId = setInterval(() => {
      dispatch({ type: "COUNT", payload: Date.now() - startTime });
    }, 100);

    dispatch({ type: "COUNTING_STARTED", payload: true });
}

If you are in a Function component, you can use the hook useRef to get the same behavior :

const timerIdRef = useRef(null);

const handleToggle = () => {
  clearInterval(timerIdRef.current);

  if (AppState.countingStarted) {
     dispatch({type: "COUNTING_STARTED",payload: false});   
  } else {
    // store the interval id into the current property
    timerIdRef.current = setInterval(() => {
      dispatch({ type: "COUNT", payload: Date.now() - startTime });
    }, 100);

    dispatch({ type: "COUNTING_STARTED", payload: true });
}

Upvotes: 2

Brendan Gannon
Brendan Gannon

Reputation: 2652

A couple of things: what you pass to clearInterval has to be the return value from setInterval; in the first example you are passing your own function to it. The second example has this right, but you're storing the timer (the result of calling setInterval) in the scope of the toggle function, which means once the function completes, you lose the reference to it; the next time you call the function (e.g. to stop it), you re-define timer, which is not what you want.

All you need is to keep the timer variable in a scope where it will persist. For example:

let timer = null;

const handleToggle = () => {
  if (AppState.countingStarted) {
    dispatch({
      type: "COUNTING_STARTED",
      payload: false
    });
    clearInterval(timer);
  } else {
    timer = setInterval(() => {
      dispatch({ type: "COUNT", payload: Date.now() - startTime });
    }, 100);
    const startTime = Date.now() - AppState.runningTime;
    dispatch({ type: "COUNTING_STARTED", payload: true });
  }
};

This function can be called multiple times; the timer variable will persist throughout.

Upvotes: 0

Related Questions