CHRIS EKE
CHRIS EKE

Reputation: 47

Is there a reason why my clearInterval function isnt working in React?

I've created a simple timer script initialized with 10 seconds for testing. The problem I'm having is that the pause timer button isn't working as expected.

import React, { useState } from 'react';

function App() {

  const [time, updateTime] = useState(() => { return 10 });
  const [timerRunning, setTimerRunning] = useState(false);
  
  let minutes = Math.floor(time / 60);
  let seconds = time % 60;
  let interval;

  function startTimer() {
    setTimerRunning(true);
    interval = setInterval( function() {
      updateTime(previousTime => previousTime === 0 ? previousTime : previousTime - 1);
    }, 1000);
  }

  function pauseTimer() {
    setTimerRunning(false);
    clearInterval(interval);
  }

  function restartTimer() {
    setTimerRunning(false);
    updateTime(() => {return 10});
  }

  return (
    <>
      <p>{minutes > 9 ? minutes : "0" + minutes}:{seconds > 9 ? seconds : "0" + seconds}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={pauseTimer}>Pause</button>
      <button onClick={restartTimer}>Restart</button>
    </>
  )
}

export default App;

I want the pause button to pause the timer. Eventually I'll make conditional statements to have each button appear based on the state of the app and the value of time, but the pause button is my current obstacle.

I first had a separate countdown function which used a conditional to stop the time when the time matched counter (below). I thought of a less complicated way that lets me omit the counter variable (above). Im not sure which option is better, or if either is preventing the clearInterval function to work properly. The clearInterval function works within the countdown function if statement, but will not work outside of it.

import React, { useState } from 'react';

function App() {

  const [time, updateTime] = useState(() => { return 10 });
  const [timerRunning, setTimerRunning] = useState(false);
  
  let counter = 0;
  let minutes = Math.floor(time / 60);
  let seconds = time % 60;
  let interval;

  function countdown() {
    counter++;
    if ( counter === time ) {
       setTimerRunning(false);
       clearInterval(interval);
    }
      updateTime(previousTime => previousTime - 1);
  }
  

  function startTimer() {
    setTimerRunning(true);
    interval = setInterval(countdown, 1000);
  }

  function pauseTimer() {
    setTimerRunning(false);
    clearInterval(interval);
  }

  function restartTimer() {
    setTimerRunning(false);
    updateTime(() => {return 10});
  }



  return (
    <>
      <p>{minutes > 9 ? minutes : "0" + minutes}:{seconds > 9 ? seconds : "0" + seconds}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={pauseTimer}>Pause</button>
      <button onClick={restartTimer}>Restart</button>
    </>
  )
}

export default App;

Upvotes: 0

Views: 127

Answers (3)

Mohammadtz
Mohammadtz

Reputation: 90

that is my solution

instead of the startTime function, I use useEffect

  useEffect(()=>{
    interval = timerRunning && setInterval(() => {
      updateTime(previousTime => previousTime === 0 ? previousTime : previousTime - 1);
    }, 1000);
    return ()=> clearInterval(interval)
  },[timerRunning])

and in onClick Start Button

<button onClick={()=> setTimerRunning(true)}>Start</button>

I hope it is useful

Upvotes: 1

user7396942
user7396942

Reputation:

You have to use useEffect, like this:

 const handleStart = () => {
    setChangeValue(true)
  }

  const handlePause = () => {
    setChangeValue(false)
    pauseTimer()
  }

  const handleRestart = () => {
    setInitialState()
    setChangeValue(true)
  }

  useEffect(() => {
    if (changeValue) {
      const interval = setInterval(() => {
        startTimer()
      }, 100)
      return () => clearInterval(interval)
    }
  }, [changeValue])

you have three buttons to start, pause and restart, invoke these (handleStart, handlePause, handleRestart) functions with them

Upvotes: 1

Kalhan.Toress
Kalhan.Toress

Reputation: 21901

Basically you can't create let interval; and assign it a setInterval like interval = setInterval(countdown, 1000);

because on each re-render there will be new let interval;

what you need to do is create a variable which isn't change on re-redners, you can use useRef

const interval = useRef(null);
.....
function startTimer() {
    interval.current = setInterval(countdown, 1000);
}
....
function pauseTimer() {
   clearInterval(interval.current);
}

and I don't think you need const [timerRunning, setTimerRunning] = useState(false);

find a demo here

Basically when functional component re-renders it will execute from top to bottom, if you use like let counter = 0;, then on each re-render it will initialize to 0, if you need to persists your values in each re-renders you might need some hooks (Ex: useState, useRef ...), in this case useRef would do the trick (because you need only one setInterval in each re-renders and useRef will give you the previous value, it will not re-initalize like a general variable)

Upvotes: 1

Related Questions