Search code examples
javascriptreactjsclearinterval

How can I stop a timer without using useState and useEffect?


In a simple timer component, I want to start and stop it with buttons, but the interval does not stop with a simple clearInterval function. Is there something I am missing?

import React, { useState } from 'react'

export default function Timer3() {
    const [seconds, setseconds] = useState(0)

    let intervalId;

    const startTimer = () => {
        intervalId = setInterval(() => {
            setseconds((pre) => pre + 1)
        }, 1000)
    }

    const stopTimer = () => {
        clearInterval(intervalId)
    }

    return (
        <>
            {seconds}
            <button onClick={startTimer}>start</button>
            <button onClick={stopTimer}>stop</button>
        </>
    )
}

I know that in the code above, there is a bug: if I press "Start" twice, it will count twice. I am going to fix this after finding out how to make it stop. There is also a solution on the web to use useEffect to run the timer, but I am not looking for code—just a simple answer as to why it does not work.


Solution

  • The issue is that each time the state updates it re-renders the component and the intervalId variable is redeclared and any previous value is lost, so when the stop button is clicked it doesn't have a reference to the running timer's id.

    You can fix this by using a React ref to store the interval id value. React refs persist render cycle to render cycle.

    function Timer3() {
      const [seconds, setseconds] = React.useState(0);
      const intervalId = React.useRef();
    
      React.useEffect(() => {
        // Clear any running timers when component unmounts
        return () => {
          clearInterval(intervalId.current);
        }
      }, []);
    
      const startTimer = () => {
        // Clear any running timers when re-starting
        clearInterval(intervalId.current);
    
        intervalId.current = setInterval(() => {
          setseconds((pre) => pre + 1);
        }, 1000);
      };
    
      const stopTimer = () => {
        clearInterval(intervalId.current);
      };
    
      return (
        <>
          {seconds}
          <button onClick={startTimer}>start</button>
          <button onClick={stopTimer}>stop</button>
        </>
      );
    }
    
    const rootElement = document.getElementById("root");
    const root = ReactDOM.createRoot(rootElement);
    
    root.render(
      <React.StrictMode>
        <Timer3 />
      </React.StrictMode>
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
    <div id="root" />