javascriptreactjssetintervalreact-functional-componentclearinterval

clearInterval not working in React Application using functional component


I wanted to build a timer application in React using functional component and below are the requirements.

The component will display a number initialized to 0 know as counter.

The component will display a Start button below the counter number.

On clicking the Start button the counter will start running. This means the counter number will start incrementing by 1 for every one second.

When the counter is running(incrementing), the Start button will become the Pause button.

On clicking the Pause button, the counter will preserve its value (number) but stops running(incrementing).

The component will also display a Reset button. On clicking the Reset button, the counter will go to its initial value(which is 0 in our case) and stops running(incrementing).

Below is the code that I have implemented, but clearInterval doesn't seems to be working, Also how do i implement Reset Button?

Code:

import React, { useState, useEffect } from "react";

export default function Counter() {
  const [counter, setCounter] = useState(0);
  const [flag, setFlag] = useState(false);
  const [isClicked, setClicked] = useState(false);
  var myInterval;

  function incrementCounter() {
    setClicked(!isClicked);
    if (flag) {
      myInterval = setInterval(
        () => setCounter((counter) => counter + 1),
        1000
      );
      setFlag(false);
    } else {
      console.log("sasdsad");
      clearInterval(myInterval);
    }
  }

  function resetCounter() {
    clearInterval(myInterval);
    setCounter(0);
  }

  useEffect(() => {
    setFlag(true);
  }, []);

  return (
    <div>
      <p>{counter}</p>
      <button onClick={incrementCounter}>
        {isClicked ? "Pause" : "Start"}
      </button>
      <button onClick={resetCounter}>Reset</button>
    </div>
  );
}

Codesandbox link: CodeSandbox


Solution

  • I did a slightly different version that use an extra useEffect that runs on isRunning (changed name from flag) change:

    import React, { useState, useEffect, useRef } from "react";
    
    export default function Counter() {
      const [counter, setCounter] = useState(0);
      // Change initial value to `false` if you don't want
      // to have timer running on load
      // Changed `flag` name to more significant name
      const [isRunning, setIsRunning] = useState(false);
      // You don't need 2 variable for this
      //const [isClicked, setClicked] = useState(false);
    
      // Using `useRef` to store a reference to the interval
      const myInterval = useRef();
    
      useEffect(() => {
        // You had this line to start timer on load
        // but you can just set the initial state to `true`
        //setFlag(true);
        // Clear time on component dismount
        return () => clearInterval(myInterval.current);
      }, []);
    
      // useEffect that start/stop interval on flag change
      useEffect(() => {
        if (isRunning) {
          myInterval.current = setInterval(
            () => setCounter((counter) => counter + 1),
            1000
          );
        } else {
          clearInterval(myInterval.current);
          myInterval.current = null;
        }
      }, [isRunning]);
    
      // Now on click you only change the flag
      function toggleTimer() {
        setIsRunning((isRunning) => !isRunning);
      }
    
      function resetCounter() {
        clearInterval(myInterval.current);
        myInterval.current = null;
        setCounter(0);
        setIsRunning(false);
      }
    
      return (
        <div>
          <p>{counter}</p>
          <button onClick={toggleTimer}>{isRunning ? "Pause" : "Start"}</button>
          <button onClick={resetCounter}>Reset</button>
        </div>
      );
    }
    

    Demo: https://codesandbox.io/s/dank-night-wwxqz3?file=/src/Counter.js

    As a little extra i've made a version that uses a custom hook useTimer. In this way the component code is way cleaner: https://codesandbox.io/s/agitated-curie-nkjf62?file=/src/Counter.js