skip to Main Content

I used setInterval function that decreases the time value by every second to develop the timer In ReactJS.

And when I clicked the RESET button before the time value is 0, I expected the time value to update back to 10 seconds, but it didn’t work in the setInterval function.

How can I modify the time value which updated outside the setInterval function to even within the setInterval function?


Here’s the code that I experiment with.

import React, { useState, useEffect, useRef } from 'react';

function Timer() {
  let time = 10;
  const [timeText, setTimeText] = useState(time);
  const [timerOn, setTimerOn] = useState(false);

  const handleReset = () => {
    if (timerOn) {
      /* This statement can not update the time value
      within the Interval function. */
      time = 10;
      return;
    }
    setTimerOn(true);
    let timerInterval = setInterval(() => {
      setTimeText(time);
      time--;
      if (time < 0) {
        clearInterval(timerInterval);
        setTimerOn(false);
      }
    }, 1000);
  };

  return (
    <div>
      <h1>{timeText}</h1>
      <button onClick={handleReset}>RESET</button>
    </div>
  );
}

export default Timer;

Here’s the desired result:

  • If RESET button was clicked when the time value is greater than 1, it changes back to 10.

Current result:

[enter image description here][1]

This question is related, but it doesn’t answer my question of how to update the time value within the setInterval function.

2

Answers


  1. I’ve checked your code again, because my first comment was actually wrong:

    I made a few changes in your code:

    import React, { useState } from 'react';
    
    function Timer() {
      // removed the const, you dont need it
      const [timeText, setTimeText] = useState(10);
      const [timerOn, setTimerOn] = useState(false);
    
      const handleReset = () => {
        if (timerOn) {
          // set the state here directly
          setTimeText(10);
          return;
        }
        setTimerOn(true);
        let timerInterval = setInterval(() => {
          // update the state by subtract 1 from it.
          // we need to wrap this in a function, because setState() is async
          setTimeText((state) => state - 1);
          if (timeText < 0) {
            clearInterval(timerInterval);
            setTimerOn(false);
          }
        }, 1000);
      };
    
      return (
        <div>
          <h1>{timeText}</h1>
          <button onClick={handleReset}>RESET</button>
        </div>
      );
    }
    
    Login or Signup to reply.
  2. It’s a problem with reference. Inside setInterval closure variable time refers to a different variable than time at the beginning of handleReset. After first rerender variable time is created again, and this second version is used by the rest of the code, but setInterval still uses the same one variable from the beginning. Check out this example

    export function App(props) {
      let time = 10;
      const [timeText1, setTimeText1] = useState(time);
      const [timeText2, setTimeText2] = useState(time);
      
      const handleReset1 = () => {
        let timerInterval1 = setInterval(() => {
          setTimeText1(time);
          
          time--;
        }, 1000);
      };
    
      const handleReset2 = () => {
        let timerInterval2 = setInterval(() => {
          setTimeText2(time);
          
          time++;
        }, 1000);
      };
    
      return (
        <div>
          <h1>{timeText1}</h1>
          <h1>{timeText2}</h1>
          <button onClick={handleReset1}>RESET1</button>
          <button onClick={handleReset2}>RESET2</button>
        </div>
      );
    }
    

    Press first RESET and wait for rerender. Then press second RESET. Both setIntervals use time, but it is completely different time, because of the rerender between them.
    Try to press both of them at the same time, they get stuck, because since no-rerender between them, they will use the same time variable.

    You could make it work, by assuring that there will be only one time that changes only value, but not reference (even after rerender). You can achieve it by using useRef. However, keep in mind it’s more for educational(?) purpose than production-ready code.

    export function App() {
      const time = useRef(10)
      const [timeText, setTimeText] = useState(time.current);
      const [timerOn, setTimerOn] = useState(false);
    
      const handleReset = () => {
        if (timerOn) {
          /* This statement can not update the time value
          within the Interval function. */
          time.current = 10;
          return;
        }
        setTimerOn(true);
        let timerInterval = setInterval(() => {
          setTimeText(time.current);
          time.current--;
          if (time.current < 0) {
            clearInterval(timerInterval);
            setTimerOn(false);
          }
        }, 1000);
      };
    
      return (
        <div>
          <h1>{timeText}</h1>
          <button onClick={handleReset}>RESET</button>
        </div>
      );
    }
    

    Approach of @MoreTags is a lot better for this kind of problem, but I wanted to show why it works the way it does.

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