Home > Mobile >  countdown button using useRef - react js
countdown button using useRef - react js

Time:01-30

I'm trying to set a countdown timer using useRef, so that I prevent re-rendering. I want the countdown timer to start counting down once the start button is clicked, then once it gets to 0, stop.

I'm a beginner, so I'm not sure what I'm doing wrong. I saw this Start the timer when button is clicked in React.Js and I tried implementing it and adding a useRef, but it's not working. Please help, thanks in advance.

    const {useState, useRef, useEffect} = React;

    const Timer = () => {
      const [secondsLeft, setSecondsLeft] = useState(10);
      const [start, setStart] = useState(false);
      
      let intervalRef = useRef();
      
      const decreaseSeconds = () => setSecondsLeft((prev) => prev - 1);
      
      useEffect(() => { 
        intervalRef.current = setInterval(decreaseSeconds, 1000);
        return () => clearInterval(intervalRef.current);
      }, []);
      
      cosnt handleClick = () => {
        let timer = null;
        if (start) { // if start button is clicked...
          setStart(true);
          intervalRef.current = setInterval(decreaseSeconds, 1000); // start timer again
        } else {
          clearInterval(intervalRef.current); // stop time if button isn't clicked
        }
      };
      
      
      return (
        <div>
          <div>{secondsLeft}</div>
          <button onClick={handleClick}>{start ? "Start" : "Cancel"}</button>
        </div>
      );
    }

    ReactDOM.render(
      <Timer />,
      document.getElementById("root")
    );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

CodePudding user response:

You need to clear interval when secondsLeft value get 0. Everytime whenever decreaseSeconds function get called check for current value and clear interval on desired condition.

 const decreaseSeconds = () => {
    setSecondsLeft((prev) => {
      if (prev === 0) {
        clearInterval(intervalRef);
        setStart(false);
        return 0;
      }
      else { 
        return prev - 1;
      }
    });
  }

The clearInterval you used under useEffect get called when this component get unmout/destroy. As this is a Good Practice to add clear timeout/interval function in useEffect so that if component get's unmount timeout/interval function get clear otherwise it will be running in backgroud and cause errors.

Check useEffect Cleanup function

CodePudding user response:

Adding to what Rohit said in his answer, here's a working example. You could use a ref to store the timer, but it's not needed in this case as you have a closure within the useEffect. Note that the dependency array includes the start state, so this useEffect will run each time start is updated.

const Timer = () => {
  const [secondsLeft, setSecondsLeft] = React.useState(10);
  const [start, setStart] = React.useState(false);
  
  const decreaseSeconds = () => {
    setSecondsLeft((prev) => {
      if (prev > 0) {
         return --prev
      } else {
        setStart(false)
        return 10
      }
    })
  };
  
  React.useEffect(() => {
    let timer
    if (start) {
      timer = setInterval(decreaseSeconds, 1000)
    } else {
      clearInterval(timer)
    }
    
    return () => clearInterval(timer)
  }, [start])
  
  const handleClick = () => setStart((prev) => !prev);
      
  return (
    <div>
      <div>{secondsLeft}</div>
      <button onClick={handleClick}>{start ? 'Cancel' : 'Start'}</button>
    </div>
  )
}

ReactDOM.createRoot(document.getElementById("root")).render(<Timer />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>

  • Related