Search code examples
javascriptreactjsreact-hookssetintervaluse-effect

How can I Pause/Stop and Interval on a function that is executed with useEffect()?


I am making a countdown timer script in react.js. Before the timer starts, a 3 or 5 seconds countdown is displayed, data for both countdown becomes from another component. I am trying to stop/pause main countdown with a button. My problem is how can I control data from a function that is executed by useEffect()? I am trying crating a state for the btn, but the scope for the state is the useEffect

import TimerForm from './components/TimerForm';

const CountDown = (props) => {

    const [timeLeft, setTimeLeft] = useState({
        minutes: 0,
        seconds: 0
    });
    const [counter, setCounter] = useState();
    const [startTimer, setStartTimer] = useState(false);
    const [result, setResult] = useState();
    let Interval;

    useEffect(() => {
        let count = null;
        if(startTimer){
            if(counter === 0){
                relog(result);
                clearInterval(count);
                return
            }
            count = setInterval(() => {
                setCounter((prevcounter) => prevcounter - 1);
            }, 1000);
            return () => clearInterval(count);
        } else {
            clearInterval(count);
        }

    }, [startTimer, counter]);

    const relog = useCallback((ForTime) => {
            console.log(testing);
            Interval = setInterval(() => {
                setTimeLeft({
                    seconds: ForTime % 60,
                    minutes: Math.floor(ForTime / 60)% 60
                });
                if(ForTime === 0){
                    clearInterval(Interval);
                    return;
                }
                ForTime--;
            },1000);      
        setStartTimer(false);
    },[]);
    
    const timerSettings = (data) => {
        setCounter(data.counter);
        setResult(data.result);
        setStartTimer(true);
    }

    return (
        <div>
        <section>
            <TimerForm onTimerSettings={timerSettings} />
            <span>{counter}</span>
            <div className="TimerClock">
                <span>{timeLeft.minutes}</span><span>:</span><span>{timeLeft.seconds}</span>
            </div>
            <div>
                <button type="button" className="btn btn-stop">Pause Button</button>
            </div>
        </section>
        </div>
    )
};

export default CountDown;```

Solution

  • create a useInterval hook used by useEffect() and useRef().

    /**
     * 
     * @param {Function} callback
     * @param {number|null} delay, stopped when delay is null
     */
    function useInterval(callback, delay) {
      const savedCallback = useRef();
    
      useEffect(() => {
        savedCallback.current = callback;
      });
    
      useEffect(() => {
        function tick() {
          savedCallback.current();
        }
    
        if (delay !== null) {
          let id = setInterval(tick, delay);
          return () => clearInterval(id);
        }
      }, [delay]);
    }
    

    use useInterval:

    function App() {
      const [delay, setDelay] = useState(null);
      const [counter, setCounter] = useState(0);
    
      useInterval(() => {
        setCounter((counter) => counter - 1);
      }, delay);
    
      const handleChangeDelay = () => {
        // stopped when delay is null
        setDelay(delay === null ? 1000 : null);
      };
    
      return (
        <div className="App">
          <p>
            <button onClick={handleChangeDelay}>trigger interval countdown</button>
          </p>
          <p>{counter}</p>
        </div>
      );
    }