Countdown timer in React

I have seen lots of countdown timers in JavaScript and wanted to get one working in React.

I have borrowed this function I found online:

secondsToTime(secs){ let hours = Math.floor(secs / (60 * 60)); let divisor_for_minutes = secs % (60 * 60); let minutes = Math.floor(divisor_for_minutes / 60); let divisor_for_seconds = divisor_for_minutes % 60; let seconds = Math.ceil(divisor_for_seconds); let obj = { "h": hours, "m": minutes, "s": seconds }; return obj; };

And then I have written this code myself

 initiateTimer = () => { let timeLeftVar = this.secondsToTime(60); this.setState({ timeLeft: timeLeftVar }) }; startTimer = () => { let interval = setInterval(this.timer, 1000); this.setState({ interval: interval }); }; timer = () => { if (this.state.timeLeft >0){ this.setState({ timeLeft: this.state.timeLeft -1 }); } else { clearInterval(this.state.interval); //this.postToSlack(); } };

Currently onclick it will set the time on screen to: Time Remaining: 1 m : 0 sBut it does not reduce it to Time Remaining: 0 m : 59 s and then Time Remaining: 0 m : 58 s etc etc

I think I need to call the function again with a different parameter. how can I go about doing this ?

Edit: I forgot to say, I would like the functionality so that I can use seconds to minutes & seconds

6

14 Answers

You have to setState every second with the seconds remaining (every time the interval is called). Here's an example:

class Example extends React.Component { constructor() { super(); this.state = { time: {}, seconds: 5 }; this.timer = 0; this.startTimer = this.startTimer.bind(this); this.countDown = this.countDown.bind(this); } secondsToTime(secs){ let hours = Math.floor(secs / (60 * 60)); let divisor_for_minutes = secs % (60 * 60); let minutes = Math.floor(divisor_for_minutes / 60); let divisor_for_seconds = divisor_for_minutes % 60; let seconds = Math.ceil(divisor_for_seconds); let obj = { "h": hours, "m": minutes, "s": seconds }; return obj; } componentDidMount() { let timeLeftVar = this.secondsToTime(this.state.seconds); this.setState({ time: timeLeftVar }); } startTimer() { if (this.timer == 0 && this.state.seconds > 0) { this.timer = setInterval(this.countDown, 1000); } } countDown() { // Remove one second, set state so a re-render happens. let seconds = this.state.seconds - 1; this.setState({ time: this.secondsToTime(seconds), seconds: seconds, }); // Check if we're at zero. if (seconds == 0) { clearInterval(this.timer); } } render() { return( <div> <button onClick={this.startTimer}>Start</button> m: {this.state.time.m} s: {this.state.time.s} </div> ); }
}
ReactDOM.render(<Example/>, document.getElementById('View'));
<script src=""></script>
<script src=""></script>
<div></div>
18

Here is a solution using hooks, Timer component, I'm replicating same logic above with hooks

import React from 'react'
import { useState, useEffect } from 'react';
const Timer = (props:any) => { const {initialMinute = 0,initialSeconds = 0} = props; const [ minutes, setMinutes ] = useState(initialMinute); const [seconds, setSeconds ] = useState(initialSeconds); useEffect(()=>{ let myInterval = setInterval(() => { if (seconds > 0) { setSeconds(seconds - 1); } if (seconds === 0) { if (minutes === 0) { clearInterval(myInterval) } else { setMinutes(minutes - 1); setSeconds(59); } } }, 1000) return ()=> { clearInterval(myInterval); }; }); return ( <div> { minutes === 0 && seconds === 0 ? null : <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1> } </div> )
}
export default Timer;
4
class Example extends React.Component { constructor() { super(); this.state = { time: {}, seconds: 5 }; this.timer = 0; this.startTimer = this.startTimer.bind(this); this.countDown = this.countDown.bind(this); } secondsToTime(secs){ let hours = Math.floor(secs / (60 * 60)); let divisor_for_minutes = secs % (60 * 60); let minutes = Math.floor(divisor_for_minutes / 60); let divisor_for_seconds = divisor_for_minutes % 60; let seconds = Math.ceil(divisor_for_seconds); let obj = { "h": hours, "m": minutes, "s": seconds }; return obj; } componentDidMount() { let timeLeftVar = this.secondsToTime(this.state.seconds); this.setState({ time: timeLeftVar }); } startTimer() { if (this.timer == 0 && this.state.seconds > 0) { this.timer = setInterval(this.countDown, 1000); } } countDown() { // Remove one second, set state so a re-render happens. let seconds = this.state.seconds - 1; this.setState({ time: this.secondsToTime(seconds), seconds: seconds, }); // Check if we're at zero. if (seconds == 0) { clearInterval(this.timer); } } render() { return( <div> <button onClick={this.startTimer}>Start</button> m: {this.state.time.m} s: {this.state.time.s} </div> ); }
}
ReactDOM.render(<Example/>, document.getElementById('View'));
<script src=""></script>
<script src=""></script>
<div></div>

Here is a simple implementation using hooks and useInterval implementation of @dan-abramov

import React, {useState, useEffect, useRef} from 'react'
import './styles.css'
const STATUS = { STARTED: 'Started', STOPPED: 'Stopped',
}
const INITIAL_COUNT = 120
export default function CountdownApp() { const [secondsRemaining, setSecondsRemaining] = useState(INITIAL_COUNT) const [status, setStatus] = useState(STATUS.STOPPED) const secondsToDisplay = secondsRemaining % 60 const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60 const minutesToDisplay = minutesRemaining % 60 const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60 const handleStart = () => { setStatus(STATUS.STARTED) } const handleStop = () => { setStatus(STATUS.STOPPED) } const handleReset = () => { setStatus(STATUS.STOPPED) setSecondsRemaining(INITIAL_COUNT) } useInterval( () => { if (secondsRemaining > 0) { setSecondsRemaining(secondsRemaining - 1) } else { setStatus(STATUS.STOPPED) } }, status === STATUS.STARTED ? 1000 : null, // passing null stops the interval ) return ( <div className="App"> <h1>React Countdown Demo</h1> <button onClick={handleStart} type="button"> Start </button> <button onClick={handleStop} type="button"> Stop </button> <button onClick={handleReset} type="button"> Reset </button> <div style={{padding: 20}}> {twoDigits(hoursToDisplay)}:{twoDigits(minutesToDisplay)}: {twoDigits(secondsToDisplay)} </div> <div>Status: {status}</div> </div> )
}
// source:
function useInterval(callback, delay) { const savedCallback = useRef() // Remember the latest callback. useEffect(() => { savedCallback.current = callback }, [callback]) // Set up the interval. useEffect(() => { function tick() { savedCallback.current() } if (delay !== null) { let id = setInterval(tick, delay) return () => clearInterval(id) } }, [delay])
}
//
const twoDigits = (num) => String(num).padStart(2, '0')

Here is the codesandbox implementation:

1

Basic idea showing counting down using Date.now() instead of subtracting one which will drift over time.

class Example extends React.Component { constructor() { super(); this.state = { time: { hours: 0, minutes: 0, seconds: 0, milliseconds: 0, }, duration: 2 * 60 * 1000, timer: null }; this.startTimer = this.start.bind(this); } msToTime(duration) { let milliseconds = parseInt((duration % 1000)); let seconds = Math.floor((duration / 1000) % 60); let minutes = Math.floor((duration / (1000 * 60)) % 60); let hours = Math.floor((duration / (1000 * 60 * 60)) % 24); hours = hours.toString().padStart(2, '0'); minutes = minutes.toString().padStart(2, '0'); seconds = seconds.toString().padStart(2, '0'); milliseconds = milliseconds.toString().padStart(3, '0'); return { hours, minutes, seconds, milliseconds }; } componentDidMount() {} start() { if (!this.state.timer) { this.state.startTime = Date.now(); this.timer = window.setInterval(() => this.run(), 10); } } run() { const diff = Date.now() - this.state.startTime; // If you want to count up // this.setState(() => ({ // time: this.msToTime(diff) // })); // count down let remaining = this.state.duration - diff; if (remaining < 0) { remaining = 0; } this.setState(() => ({ time: this.msToTime(remaining) })); if (remaining === 0) { window.clearTimeout(this.timer); this.timer = null; } } render() { return ( < div > < button onClick = { this.startTimer } > Start < /button> { this.state.time.hours }: { this.state.time.minutes }: { this.state.time.seconds }. { this.state.time.milliseconds }: < /div> ); }
}
ReactDOM.render( < Example / > , document.getElementById('View'));
<script src=""></script>
<script src=""></script>
<div></div>
2

The problem is in your "this" value. Timer function cannot access the "state" prop because run in a different context. I suggest you to do something like this:

...
startTimer = () => { let interval = setInterval(this.timer.bind(this), 1000); this.setState({ interval });
};

As you can see I've added a "bind" method to your timer function. This allows the timer, when called, to access the same "this" of your react component (This is the primary problem/improvement when working with javascript in general).

Another option is to use another arrow function:

startTimer = () => { let interval = setInterval(() => this.timer(), 1000); this.setState({ interval });
};
1

Countdown of user input

Interface Screenshotscreenshot

import React, { Component } from 'react';
import './App.css';
class App extends Component { constructor() { super(); this.state = { hours: 0, minutes: 0, seconds:0 } this.hoursInput = React.createRef(); this.minutesInput= React.createRef(); this.secondsInput = React.createRef(); } inputHandler = (e) => { this.setState({[e.target.name]: e.target.value}); } convertToSeconds = ( hours, minutes,seconds) => { return seconds + minutes * 60 + hours * 60 * 60; } startTimer = () => { this.timer = setInterval(this.countDown, 1000); } countDown = () => { const { hours, minutes, seconds } = this.state; let c_seconds = this.convertToSeconds(hours, minutes, seconds); if(c_seconds) { // seconds change seconds ? this.setState({seconds: seconds-1}) : this.setState({seconds: 59}); // minutes change if(c_seconds % 60 === 0 && minutes) { this.setState({minutes: minutes -1}); } // when only hours entered if(!minutes && hours) { this.setState({minutes: 59}); } // hours change if(c_seconds % 3600 === 0 && hours) { this.setState({hours: hours-1}); } } else { clearInterval(this.timer); } } stopTimer = () => { clearInterval(this.timer); } resetTimer = () => { this.setState({ hours: 0, minutes: 0, seconds: 0 }); this.hoursInput.current.value = 0; this.minutesInput.current.value = 0; this.secondsInput.current.value = 0; } render() { const { hours, minutes, seconds } = this.state; return ( <div className="App"> <h1 className="title"> (( React Countdown )) </h1> <div className="inputGroup"> <h3>Hrs</h3> <input ref={this.hoursInput} type="number" placeholder={0} name="hours" onChange={this.inputHandler} /> <h3>Min</h3> <input ref={this.minutesInput} type="number" placeholder={0} name="minutes" onChange={this.inputHandler} /> <h3>Sec</h3> <input ref={this.secondsInput} type="number" placeholder={0} name="seconds" onChange={this.inputHandler} /> </div> <div> <button onClick={this.startTimer} className="start">start</button> <button onClick={this.stopTimer} className="stop">stop</button> <button onClick={this.resetTimer} className="reset">reset</button> </div> <h1> Timer {hours}: {minutes} : {seconds} </h1> </div> ); }
}
export default App;
1

I had the same problem and I found this npm package for a countdown.

  1. install the package

    npm install react-countdown --saveor

    yarn add react-countdown

  2. import the package to your file

    import Countdown from 'react-countdown';

  3. call the imported "Countdown" inside a render method and pass a date

    <Countdown date={new Date('2021-09-26T10:05:29.896Z').getTime()}>or

    <Countdown date={new Date("Sat Sep 26 2021")}>

Here is an example for you.

import React from "react";
import ReactDOM from "react-dom";
import Countdown from "react-countdown";
// Random component
const Completionist = () => <span>You are good to go!</span>;
ReactDOM.render( <Countdown date={new Date('2021-09-26T10:05:29.896Z').getTime()}> <Completionist /> </Countdown>, document.getElementById("root")
);

you can see the detailed document here

functionality : 1)Start 2)Reset

functional component

import {useState, useCallback} from 'react';
const defaultCount = 10;
const intervalGap = 300;
const Counter = () => { const [timerCount, setTimerCount] = useState(defaultCount); const startTimerWrapper = useCallback((func)=>{ let timeInterval: NodeJS.Timer; return () => { if(timeInterval) { clearInterval(timeInterval) } setTimerCount(defaultCount) timeInterval = setInterval(() => { func(timeInterval) }, intervalGap) } }, []) const timer = useCallback(startTimerWrapper((intervalfn: NodeJS.Timeout) => { setTimerCount((val) => { if(val === 0 ) { clearInterval(intervalfn); return val } return val - 1 }) }), []) return <> <div> Counter App</div> <div> <button onClick={timer}>Start/Reset</button></div> <div> {timerCount}</div> </>
}
export default Counter;
1

When you are using functional components the above code is a good option to do it:

import React, { useState, useEffect } from "react";
import { MessageStrip } from "@ui5/webcomponents-react";
import "./Timer.scss";
const nMinuteSeconds = 60;
const nSecondInMiliseconds = 1000;
const convertMinutesToMiliseconds = (minute) => minute * nMinuteSeconds * nSecondInMiliseconds;
const convertMilisecondsToHour = (miliseconds) => new Date(miliseconds).toISOString().slice(11, -5);
export default function Counter({ minutes, onTimeOut }) { let [timerCount, setTimerCount] = useState( convertMinutesToMiliseconds(minutes) ); let interval; useEffect(() => { if (interval) { clearInterval(interval); } interval = setInterval(() => { if (timerCount === 0 && interval) { onTimeOut(); clearInterval(interval); } setTimerCount((timerCount -= nSecondInMiliseconds)); }, nSecondInMiliseconds); }, []); return ( <> <MessageStrip design="Information" hide-close-button> Time left: {convertMilisecondsToHour(timerCount)} </MessageStrip> </> );
}

The one downside with setInterval is that it can slow down the main thread. You can do a countdown timer using requestAnimationFrame instead to prevent this. For example, this is my generic countdown timer component:

class Timer extends Component { constructor(props) { super(props) // here, getTimeRemaining is a helper function that returns an // object with { total, seconds, minutes, hours, days } this.state = { timeLeft: getTimeRemaining(props.expiresAt) } } // Wait until the component has mounted to start the animation frame componentDidMount() { this.start() } // Clean up by cancelling any animation frame previously scheduled componentWillUnmount() { this.stop() } start = () => { this.frameId = requestAnimationFrame(this.tick) } tick = () => { const timeLeft = getTimeRemaining(this.props.expiresAt) if (timeLeft.total <= 0) { this.stop() // ...any other actions to do on expiration } else { this.setState( { timeLeft }, () => this.frameId = requestAnimationFrame(this.tick) ) } } stop = () => { cancelAnimationFrame(this.frameId) } render() {...}
}
1

Here is a TypeScript version of CountDown Timer in React. I used code of brother Masood and M.Georgiev

import React, {useState, useEffect, useCallback} from "react";
const Minute_to_Seconds = 60;
const Seconds_to_milliseconds = 1000;
export interface CounterProps { minutes:number, statusAlert: (status: string)=>void,
}
export interface TimerProps { initialMinute: number, initialSeconds: number,
}
const Counter: React.FC<CounterProps> = (props) => { const convert_Minutes_To_MiliSeconds = (minute:number) => { return minute * Minute_to_Seconds * Seconds_to_milliseconds; } const convert_Mili_Seconds_To_Hour = (miliseconds:number) => { return new Date(miliseconds).toISOString().slice(11, -5); } const convert_Mili_Seconds_To_Minute = (miliseconds:number) => { return new Date(miliseconds).toISOString().slice(11, -5); } const [timer_State, setTimer_State]=useState(0); const [timerCount, setTimerCount] = useState(convert_Minutes_To_MiliSeconds(props.minutes)); useEffect(() => { if (timerCount > 0) { const interval = setInterval(() => { if (timer_State === 0) { props.statusAlert("start"); setTimer_State(1); } let tempTimerCount = timerCount; tempTimerCount -= Seconds_to_milliseconds; setTimerCount(tempTimerCount); }, (timer_State === 0) ? 0 : Seconds_to_milliseconds ); return () => { clearInterval(interval); } } else{ props.statusAlert("end"); } }, [ timer_State, timerCount, props, ]); return ( <p> Time left: {convert_Mili_Seconds_To_Hour(timerCount)} </p> );
}
const Timer: React.FC<TimerProps> = (props) => { const [ minutes, setMinutes ] = useState(props.initialMinute); const [seconds, setSeconds ] = useState(props.initialSeconds); useEffect(()=>{ const myInterval = setInterval(() => { if (seconds > 0) { setSeconds(seconds - 1); } if (seconds === 0) { if (minutes === 0) { clearInterval(myInterval) } else { setMinutes(minutes - 1); setSeconds(59); } } }, 1000) return ()=> { clearInterval(myInterval); }; }); return ( <div> { ((minutes === 0) && (seconds === 0)) ? "Press F5 to Refresh" : <h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1> } </div> )
}
const RCTAPP=()=> { const status_Alert2=(status: string)=> { console.log("__________________________==================== status: ", status); if (status==="start"){ alert("Timer started"); } else{ alert("Time's up"); } } return ( <div style={{textAlign: "center"}}> <Counter minutes={1} // minutes={0.1} statusAlert={status_Alert2} /> <Timer initialMinute={0} initialSeconds={30} /> </div> );
}
export default RCTAPP;

In react native:

Usage:

timestamp prop must be in seconds

const refTimer = useRef(); const timerCallbackFunc = timerFlag => { // Setting timer flag to finished console.warn( 'You can alert the user by letting him know that Timer is out.', ); }; <Timer ref={refTimer} timestamp={moment(item?.time_left).diff(moment(), 'seconds')} timerCallback={timerCallbackFunc} textStyle={styles.timerTextAHL} />

Timer.js

import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle,
} from 'react';
import { Text, View } from 'react-native';
const Timer = forwardRef((props, ref) => { // For Total seconds const [timeStamp, setTimeStamp] = useState( props.timestamp ? props.timestamp : 0, ); // Delay Required const [delay, setDelay] = useState(props.delay ? props.delay : 1000); // Flag for informing parent component when timer is over const [sendOnce, setSendOnce] = useState(true); // Flag for final display time format const [finalDisplayTime, setFinalDisplayTime] = useState(''); useInterval(() => { if (timeStamp > 0) { setTimeStamp(timeStamp - 1); } else if (sendOnce) { if (props.timerCallback) { props.timerCallback(true); } else { console.log('Please pass a callback function...'); } setSendOnce(false); } setFinalDisplayTime(secondsToDhms(timeStamp)); }, delay); function secondsToDhms(seconds) { seconds = Number(seconds); var d = Math.floor(seconds / (3600 * 24)); var h = Math.floor((seconds % (3600 * 24)) / 3600); var m = Math.floor((seconds % 3600) / 60); var s = Math.floor(seconds % 60); var dDisplay = d > 0 ? d + 'd ' : ''; var hDisplay = h > 0 ? h + 'h ' : ''; var mDisplay = m > 0 ? m + 'm ' : ''; var sDisplay = s > 0 ? s + 's ' : ''; return dDisplay + hDisplay + mDisplay + sDisplay; } const refTimer = useRef(); useImperativeHandle(ref, () => ({ resetTimer: () => { // Clearing days, hours, minutes and seconds // Clearing Timestamp setTimeStamp(props.timestamp); setSendOnce(true); }, })); return ( <View ref={refTimer} style={props.containerStyle}> <Text style={props.textStyle}>{sendOnce ? finalDisplayTime : '0'}</Text> </View> );
});
function useInterval(callback, delay) { const savedCallback = useRef(); // Remember the latest function. useEffect(() => { savedCallback.current = callback; }, [callback]); // Set up the interval. useEffect(() => { function tick() { savedCallback.current(); } if (delay !== null) { const id = setInterval(tick, delay); return () => { clearInterval(id); }; } }, [delay]);
}
export default Timer;

enter image description here

Here's a simple implementation using a custom hook:

import * as React from 'react';
// future time from where countdown will start decreasing
const futureTime = new Date( Date.now() + 5000 ).getTime(); // adding 5 seconds
export const useCountDown = (stop = false) => { const [time, setTime] = React.useState( futureTime - Date.now() ); // ref to store interval which we can clear out later // when stop changes through parent component (component that uses this hook) // causing the useEffect callback to trigger again const intervalRef = React.useRef<NodeJS.Timer | null>(null); React.useEffect(() => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; return; } const interval = intervalRef.current = setInterval(() => { setTime(futureTime - Date.now()); }, 1000); return () => clearInterval(interval); }, [stop]); return getReturnValues(time);
};
const getReturnValues = (countDown: number) => { const days = Math.floor(countDown / (1000 * 60 * 60 * 24)); const hours = Math.floor( (countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) ); const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((countDown % (1000 * 60)) / 1000); return [days, hours, minutes, seconds];
};

Example of using this hook:

function App() { const [timerStopped, stopTimer] = React.useState(false); const [,hours,minutes,seconds] = useCountDown(timerStopped); // to stop the interval if( !timerStopped && minutes + seconds <= 0 ) { stopTimer(true); } return ( <div className="App"> Time Left: {hours}:{minutes}:{seconds} { timerStopped ? ( <h1>Time's up</h1> ) : null } </div> );
}

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like