Create a countdown timer in React ⏱
March 02, 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 its 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);}}