Create a countdown timer in React ⏱

Sumedh Chakravorty
3 min readApr 26, 2020

We will be using functional component + hook for building this component. setInterval will be used for core timer.

Create a component

The code (kinda) looks like this. the timer (state) tracks the timer status. user can start/play or pause the timer.

import React from 'react'function Timer() {const [timer, setTimer] = React.useState({name: '',isPaused: false,time: 0,timeRemaining: 0})const handleTimerCreate = e => {}return <React.Fragment><input type="text" onChange={handleTimerCreate} /><div>// render current timer details and actions</div></React.Fragment>}export default Timer;

timer logic

we are using browsers built-in setInterval for timer logic. we are also keeping track of the timerHandler.

const handleStart = e => {const handle = setInterval(updateTimeRemaining, 1000);setTimer({ ...timer, isPaused: false, timerHandler: handle })}

we update the state with the interval handle, so that we can later clear it when it times out or it’s paused.

const handlePause = e => {clearInterval(timer.timerHandler)setTimer({ ...timer, isPaused: true })}

timer card and actions

The timner card is visible when name and time is provided, when timeRemaining is 0 we add styles for timeout animations. the actions available on the timer are start/play and pause.

{timer.name && timer.time &&<div className={timer.timeRemaining === 0 ? 'time-out':''} style={classes.card}><h3>{timer.name}</h3><h2>{timer.time}</h2>{timer.isPaused && <div onClick={handleStart}><img alt="play" src={play}/></div>}{!timer.isPaused && <div onClick={handlePause}><img alt="pause" src={pause}/> </div>}<h1>{`Time remaining ${timer.timeRemaining}`}</h1></div>}

Action : start

In the start/play action we are using setInterval with callback and timeout set to 1000ms. The callback handles the the updating of timeRemaining.

const updateTimeRemaining = e => {setTimer(prev => {return { ...prev, timeRemaining: prev.timeRemaining - 1 }})}

Action : pause

In pause handler, we are clearning the timerHandler and setting the timer as paused.

const handlePause = e => {clearInterval(timer.timerHandler)setTimer({ ...timer, isPaused: true })}

Handle timeout

We are using useEffect hook for tracking timeRemaining and updating timer state based on timeRemaining.

React.useEffect(() => {if (timer.timeRemaining === 0) {clearInterval(timer.timerHandler)}}, [timer.timeRemaining, timer.timerHandler])

The final component

import React from 'react';import './App.css';import pause from './pause.svg'import play from './play.svg'const classes = {card: {height: '20em',width: '10em',border: '1px solid #5353ca',borderRadius: '10px',backgroundColor: '#5353ca',margin: 'auto',marginTop: '5em',color: 'white',padding: '10px',boxShadow:'-1px 1px 7px 3px #0000003d '}}function Timer() {const [timer, setTimer] = React.useState({name: 'ee',isPaused: true,time: 100,timeRemaining: 100,timerHandler: null})const handleTimerCreate = e => {setTimer({...timer,name: e.target.value})}const handleTimeChange = e => {setTimer({...timer,time: Number(e.target.value),timeRemaining: Number(e.target.value),})}React.useEffect(() => {if (timer.timeRemaining === 0) {clearInterval(timer.timerHandler)}}, [timer.timeRemaining, timer.timerHandler])const updateTimeRemaining = e => {setTimer(prev => {return { ...prev, timeRemaining: prev.timeRemaining - 1 }})}const handleStart = e => {const handle = setInterval(updateTimeRemaining, 1000);setTimer({ ...timer, isPaused: false, timerHandler: handle })}const handlePause = e => {clearInterval(timer.timerHandler)setTimer({ ...timer, isPaused: true })}return <React.Fragment><input value={timer.name} type="text" onChange={handleTimerCreate} /><input value={timer.time} type="number" onChange={handleTimeChange} />{timer.name && timer.time && <div className={timer.timeRemaining === 0 ? 'time-out':''} style={classes.card}><h3>{timer.name}</h3><h2>{timer.time}</h2>{timer.isPaused && <div onClick={handleStart}><img alt="play" src={play}/></div>}{!timer.isPaused && <div onClick={handlePause}><img alt="pause" src={pause}/> </div>}<h1>{`Time remaining ${timer.timeRemaining}`}</h1></div>}</React.Fragment>}

styles

.time-out {animation: shake 0.92s cubic-bezier(.36,.07,.19,.97) infinite;transform: translate3d(0, 0, 0);backface-visibility: hidden;perspective: 1000px;}@keyframes shake {10%, 90% {transform: translate3d(-1px, 0, 0);}20%, 80% {transform: translate3d(2px, 0, 0);}30%, 50%, 70% {transform: translate3d(-4px, 0, 0);}40%, 60% {transform: translate3d(4px, 0, 0);}}

Originally published at https://sumedh.netlify.app.

--

--

Sumedh Chakravorty

Product Manager for Oracle Visual Builder.I talk about Oracle JET, VBCS, React, React Native, Spring Boot. Views are my own.