skip to Main Content

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.

3

Answers


  1. 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" />
    Login or Signup to reply.
  2. You can store intervalId in a ref useRef instead of re declaring it each time:

    import React, { useState, useRef } from 'react';
    
    export default function Timer3() {
        const [seconds, setSeconds] = useState(0);
        const intervalId = useRef(null);
    
        const startTimer = () => {
            if (intervalId.current) return; 
            intervalId.current = setInterval(() => {
                setSeconds((prev) => prev + 1);
            }, 1000);
        };
    
        const stopTimer = () => {
            clearInterval(intervalId.current);
            intervalId.current = null; 
        };
    
        return (
            <>
                {seconds}
                <button onClick={startTimer}>Start</button>
                <button onClick={stopTimer}>Stop</button>
            </>
        );
    }
    
    Login or Signup to reply.
  3. intervalId resets each time the component re-renders as its regular variable.

    You can use useRef instead of useState and useEffect, useRef keeps the value and do not re-render when changes.

    const intervalId = useRef(null);

    In startTimer, assign the interval to intervalId.current instead of just intervalId.

    In stopTimer, call clearInterval(intervalId.current).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search