DEV Community

How to Create a Simple React Countdown Timer

Zhiyue Yi on January 07, 2020

Visit my Blog for the original post: How to Create a Simple React Countdown Timer A Few Words in Front Today I am going to share one in...
Collapse
 
arpan_banerjee7 profile image
Arpan Banerjee

I think you just complicated the things unnecessarily, always remember this rule of thumb

  • whenever you want to update the state based on the previous state , pass a function to the setState() method, then react will make sure it will call the setState() with the latest value of the state variable.
import React from "react";
export default function App() {
  const [counter, setCounter] = React.useState(60);

  // Second Attempts
  React.useEffect(() => {
     counter>0 && setInterval(() => {
        setCounter((time)=>time-1);
      }, 1000);
  }, []);

  return (
    <div className="App">
      <div>Countdown: {counter}</div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This solution works, but it has a problem, the countdown never stops.
To make it stop at 0, we will make use of useRef hook, it will store the reference to the setInterval, whihc we can clear when the timer is 0.The complete solution would look like this.

import React from "react";
export default function App() {
  const [timer, setTimer] = React.useState(10);
  const id =React.useRef(null);
  const clear=()=>{
  window.clearInterval(id.current)
}
  React.useEffect(()=>{
     id.current=window.setInterval(()=>{
      setTimer((time)=>time-1)
    },1000)
    return ()=>clear();
  },[])

  React.useEffect(()=>{
    if(timer===0){
      clear()
    }

  },[timer])


  return (
    <div className="App">

     <div>Time left : {timer} </div>

    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

codesandbox.io/s/reverent-bash-bxrnv

please let me know your thoughts on this.

Collapse
 
ankit_choudhary profile image
ankit kumar • Edited

Hi Arpan , i think if you pass Timer as dependency in useEffect, that will do the job simply.
you dont need to use useRef and 2nd useEffect.
let me know your thoughts.

React.useEffect(() => {
const TimerInt = timer >0 && setInterval(() => {
setCounter((time)=>time-1);
}, 1000);
return () => {
clearInterval(TimerInt)
}
}, [timer]);

Collapse
 
hussamkhatib profile image
mohammed hussam

hey this is a very nice implementation , but consider i want the timer to completely stop at 0 , and then we want the countdown again , if we change the value of timer then it won't do anything.
Any idea how we can overcome this

Collapse
 
zhiyueyi profile image
Zhiyue Yi

Your solution is more intuitive than mine. Nice one!

Collapse
 
arpan_banerjee7 profile image
Arpan Banerjee

Thank you!

Collapse
 
seanmclem profile image
Seanmclem

I had good success creating a timer by storing the current datetime when starting the timer, and then on every interval getting the new current datetime again and doing the math to find the difference. If any computation takes longer than it should - it doesn't matter. The date time is always valid

Collapse
 
zhiyueyi profile image
Zhiyue Yi

Hey Sean! Interesting thoughts! I tried your approach, and it also works!

Collapse
 
seanmclem profile image
Seanmclem

Thanks! That's awesome

Collapse
 
rmar72 profile image
Ruben Solorio • Edited

Nice work! Learned good stuff and folks' comments! <3 Thanks!

Will point out that for more comprehensive timers a major problem with setIntervals or setTimeouts is that they're unreliable when switching between tabs/phone locks. Usually a 1000ms lag but can be arbitrary.
Solutions I've seen so far to the inaccuracy issue will be based on Date objects, Performance interface, requestAnimationFrame etc

Collapse
 
ferhatavdic profile image
FerhatAvdic

You sir are amazing! You saved me so much time at work with this valuable post!! I needed to build a countdown from a certain date in days hours minutes and seconds.

Collapse
 
zhiyueyi profile image
Zhiyue Yi

Thank you! Glad it helps!

Collapse
 
ankit_choudhary profile image
ankit kumar

hi @zhiyueyi , let me know your thoughts about this.

React.useEffect(() => {
const TimerInt = timer >0 && setInterval(() => {
setCounter((time)=>time-1);
}, 1000);
return () => {
clearInterval(TimerInt)
}
}, [timer]);

Collapse
 
elramus profile image
Luke Ramus • Edited

Cool, thank you! Only flaw is that if the user switches to a different tab, the timer pauses. Any thoughts on how to get around that, or is that just a limitation of how JavaScript runs in the browser?

Collapse
 
trangcongthanh profile image
Thành Trang • Edited

Whats limitation?

If your component will unmount, you should lift your count state up (or make it global) to store the current count. Then when remount, init your count state with the previous count. It'll start counting at the previous position.

If you component will not unmount, just clear your current timeout when the tab lost focus. It'll pause. When it get focused again, set your timeout back.

Collapse
 
siddrc profile image
Siddharth Roychoudhury

I like the approach of the author, its simple and clean..

Collapse
 
belclei profile image
Belclei

Hi!
If instead "setInterval" you use "setTimeout", your timer works perfectly in a simple code like your first try.

Collapse
 
sbyeol3 profile image
SaetByeol Ahn

Thanks 😂!!! It's Helpful to me <3

Collapse
 
sonhip profile image
Son_Tran_Van

realy like your post, it's really useful, thanks!!!!!!