how do I clearInterval on-click, with React Hooks?

--- added @ 2019-02-11 15:58 ---

A good pattern to use setInterval with Hooks API:

https://overreacted.io/making-setinterval-declarative-with-react-hooks/


--- origin answer ---

Some issues:

  1. Do not use non-constant variables in the global scope of any modules. If you use two instances of this module in one page, they’ll share those global variables.

  2. There’s no need to clear timer in the “else” branch because if the timerOn change from true to false, the return function will be executed.

A better way in my thoughts:

import { useState, useEffect } from 'react';

export default (handler, interval) => {
  const [intervalId, setIntervalId] = useState();
  useEffect(() => {
    const id = setInterval(handler, interval);
    setIntervalId(id);
    return () => clearInterval(id);
  }, []);
  return () => clearInterval(intervalId);
};

Running example here:

https://codesandbox.io/embed/52o442wq8l?codemirror=1


In this example, we add a couple of things...

  1. A on/off switch for the timeout (the 'running' arg) which will completely switch it on or off

  2. A reset function, allowing us to set the timeout back to 0 at any time:

    If called while it's running, it'll keep running but return to 0. If called while it's not running, it'll start it.

const useTimeout = (callback, delay, running = true) => {
  // save id in a ref so we make sure we're always clearing the latest timeout
  const timeoutId = useRef('');

  // save callback as a ref so we can update the timeout callback without resetting it
  const savedCallback = useRef();
  useEffect(
    () => {
      savedCallback.current = callback;
    },
    [callback],
  );

  // clear the timeout and start a new one, updating the timeoutId ref
  const reset = useCallback(
    () => {
      clearTimeout(timeoutId.current);

      const id = setTimeout(savedCallback.current, delay);
      timeoutId.current = id;
    },
    [delay],
  );

  // keep the timeout dynamic by resetting it whenever its' deps change
  useEffect(
    () => {
      if (running && delay !== null) {
        reset();

        return () => clearTimeout(timeoutId.current);
      }
    },
    [delay, running, reset],
  );

  return { reset };
};

So in your example above, we could use it like so...

const UseStateExample = ({delay}) => {
    // count logic
    const initCount = 0
    const [count, setCount] = useState(initCount)
    
    const incrementCount = () => setCount(prev => prev + 1)
    const decrementCount = () => setCount(prev => prev - 1)
    const resetCount = () => setCount(initCount)

    // timer logic
    const [timerOn, setTimerOn] = useState(false)
    const {reset} = useTimeout(incrementCount, delay, timerOn)

    const startTimer = () => setTimerOn(true)
    const stopTimer = () => setTimerOn(false)
    
    return (
        <div>
            <h2>Notes:</h2>
            <p>New function are created on each render</p>
            <br />
            <h2>count = {count}</h2>
            <button onClick={incrementCount}>Increment</button>
            <br />
            <button onClick={decrementCount}>Decrement</button>
            <br />
            <button onClick={startTimer}>Set Interval</button>
            <br />
            <button onClick={stopTimer}>Stop Interval</button>
            <br />
            <button onClick={reset}>Start Interval Again</button>
            <br />

        </div>
    );
}