React Countdown Timer
How to create a countdown timer component in React
Overview:
In this tutorial, we will build a simple JavaScript timer as a React Component. My initial approach was to use the setInterval
function and have it count the number of seconds that had passed from the start of the timer. However, due to the single-threaded nature of JavaScript, I ran into a problem where the clock would be behind the actual time if there were other heavier processes running on the same thread. To prevent this issue, we will use the JavaScript Date
object to keep track of our time. There are definitely multiple other approaches you can use to accomplish the same result.
User Interface:
The first thing we will do is create a simple UI. All we need are two buttons to start and pause the clock. We also need a digital display to show our timer.
import React, { useState } from 'react'
function Timer() {
const [ display, setDisplay ] = useState("00:10")
const startClockFn = () => {}
const pauseClockFn = () => {}
return (
<div style={{ textAlign: 'center' }}>
<h1>{display}</h1>
<button onClick={startClockFn}>
Start
</button>
 
<button onClick={pauseClockFn}>
Pause
</button>
</div>
)
}
export default Timer
The above code builds a simple UI for our component. We will use the display
state to manipulate the display of our clock. The two buttons should call their respective functions to start and pause the timer. At this point, the display should look as follows:
Start Clock Function:
startClockFn()
is the core engine of this component. This function will create a variable called start
when the start button is clicked where the current date-time provided by the Date
object will be stored. Using the variable as the starting point we will create a new date-time every second using the setInterval
function.
const [ totalTime ] = useState(10)
const startClockFn = () => {
const start = new Date()
setInterval(() => {
let current
current = Number(((new Date() - start) / 1000).toFixed())
current = totalTime - current
let mins = (current / 60).toString().split(".")[0].padStart(2, "0")
let secs = (current % 60).toString().padStart(2, "0")
setDisplay(`${mins}:${secs}`)
}, 1000)
}
In the code above, we created a new state to store our total time. Inside startClockFn()
we are using the current
variable to manipulate our time every second. When we subtract the starting time from a new Date
object, we get a difference of about a second which we convert from milliseconds to a single-digit number. This number is the total number of seconds that have passed from the start time. We can subtract this from the totalTime
to get the current time which we can display on the screen.
We can get the mins
by dividing the current time by 60. The string padStart
function lets us display 0 minutes as "00"
or "01"
. We do the same thing for secs
, but instead of dividing by 60, we mod it by 60. After executing this code, our program should look as follows:
Stop Clock Function:
The clock above does not stop at 00:00. For that, we need to keep an eye on the number of seconds that have passed from the start and stop the clock when the number of seconds is equal to the totalTime
.
The first thing we need here is a state variable to store the seconds passed from the initial start.
const [secsFromInitialStart, setSecsFromInitialStart] = useState(0)
To keep track of the seconds passed from the start till now, we will use the current
variable in the startClockFn()
before turning it into the timer display.
const startClockFn = () => {
const start = new Date()
setInterval(() => {
let current
current = Number(((new Date() - start) / 1000).toFixed())
setSecsFromInitialStart(current)
current = totalTime - current
let mins = (current / 60).toString().split(".")[0].padStart(2, "0")
let secs = (current % 60).toString().padStart(2, "0")
setDisplay(`${mins}:${secs}`)
}, 1000)
}
Note that the value of current
before subtracting from the totalTime
gives us the number of seconds passed from the initial start.
Next, we need to keep track of secsFromInitialStart
during runtime. For that, we will use the effect hook from React.
import React, { useState, useEffect } from 'react'
The function inside the effect hook should call the stopClockFn()
if the secsFromInitialStart
is equal to the totalTime
.
useEffect(() => {
if(Number(secsFromInitialStart) === Number(totalTime)) {
stopClockFn()
}
}, [secsFromInitialStart])
I am using the Number
object just to avoid any pitfalls of comparing numbers with strings. Next, in the stopClockFn
, we need to clear the setInterval
function for the clock to actually stop. To achieve that, we can simply call the clearInterval()
function.
One problem with the clearInterval()
function is that it needs the intervalID
of the setInterval()
we are trying to stop. We can get the intervalId
by simply returning a value from the setInterval()
. Since we need the intervalId
in an effect hook, we will create a new state variable to store it.
const [ clock, setClock ] = useState()
Here, clock
is going to be our interval ID. To set it, we will modify the startClockFn
and use the value returned from the setInterval()
function as the ID.
const startClockFn = () => {
const start = new Date()
setClock(setInterval(() => {
let current
current = Number(((new Date() - start) / 1000).toFixed())
setSecsFromInitialStart(current)
current = totalTime - current
let mins = (current / 60).toString().split(".")[0].padStart(2, "0")
let secs = (current % 60).toString().padStart(2, "0")
setDisplay(`${mins}:${secs}`)
}, 1000))
}
Then, we simply need to call the clearInterval(clock)
function in our stopClockFn()
.
const stopClockFn = () => {
clearInterval(clock)
}
At this point, our clock should stop at 00:00
and should look as follows:
Pause Clock Function
If you haven’t noticed yet, the “Pause” button does not do anything yet. For the pause button to work, we will use the same logic as the stopClockFn()
.
const pauseClockFn = () => {
clearInterval(clock)
}
With this function, we can press the pause button to pause the clock. However at the moment, instead of continuing to count down, the timer will restart from its starting time.
The issue here is that every time we press the “Start” button, there is a new start
value, and every new second after that will be the seconds passed from the time we pressed the “Start” button most recently.
To solve the problem, we need to keep track of two things. The first is the number of seconds that have passed from the last time the user hit pause. The second is whether the user has hit pause.
To keep track of whether the user hits pause, we will introduce a new boolean state variable.
const [ clockPaused, setClockPaused ] = useState(false)
We can set the value of clockPaused
to true
inside the pauseClockFn()
.
const pauseClockFn = () => {
setClockPaused(true)
clearInterval(clock)
}
Now, inside the startClockFn()
, we need to create a secsFromLastPaused
local variable to store the seconds passed from the most recent “Pause” press. Then, by checking if the clock is currently paused, we can add secsFromInitialStart
to secsFromLastPaused
. Finally, to put it all together, we need to add the updated secsFromLastPaused
value to the current
value before saving it to secsFromInitialStart
.
const startClockFn = () => {
const start = new Date()
let secsFromLastPaused = 0
if(clockPaused) {
secsFromLastPaused += secsFromInitialStart
setClockPaused(false)
}
setClock(setInterval(() => {
let current
current = Number(((new Date() - start) / 1000).toFixed())
current += secsFromLastPaused
setSecsFromInitialStart(current)
current = totalTime - current
let mins = (current / 60).toString().split(".")[0].padStart(2, "0")
let secs = (current % 60).toString().padStart(2, "0")
setDisplay(`${mins}:${secs}`)
}, 1000))
}
After adding the code above, the program should look as follows:
In a nutshell:
Using a simple setInterval()
function to create a countdown timer can be troublesome due to the single-threaded nature of JavaScript. However, we built a timer component in React using the Date
object and manipulating the time according to our use cases. This is an open-ended component that you can build upon or just use some parts of it. There are bugs that we haven’t addressed in this tutorial, but the component should be a good starting point if you are trying to incorporate a working timer in your project.
This is the complete code for the component:
import React, { useState, useEffect } from 'react'
function Timer() {
const [ display, setDisplay ] = useState("00:10")
const [ totalTime ] = useState(10)
const [secsFromInitialStart, setSecsFromInitialStart] = useState(0)
const [ clock, setClock ] = useState()
const [ clockPaused, setClockPaused ] = useState(false)
const startClockFn = () => {
const start = new Date()
let secsFromLastPaused = 0
if(clockPaused) {
secsFromLastPaused += secsFromInitialStart
setClockPaused(false)
}
setClock(setInterval(() => {
let current
current = Number(((new Date() - start) / 1000).toFixed())
current += secsFromLastPaused
setSecsFromInitialStart(current)
current = totalTime - current
let mins = (current / 60).toString().split(".")[0].padStart(2, "0")
let secs = (current % 60).toString().padStart(2, "0")
setDisplay(`${mins}:${secs}`)
}, 1000))
}
useEffect(() => {
if(Number(secsFromInitialStart) === Number(totalTime)) {
stopClockFn()
}
}, [secsFromInitialStart])
const stopClockFn = () => {
clearInterval(clock)
}
const pauseClockFn = () => {
setClockPaused(true)
clearInterval(clock)
}
return (
<div style={{ textAlign: 'center' }}>
<h1>{display}</h1>
<button onClick={startClockFn}>
Start
</button>
 
<button onClick={pauseClockFn}>
Pause
</button>
</div>
)
}
export default Timer
Happy Coding!