In this tutorial, you will learn how to build a timer using react hooks. React hooks is the new way of building react apps and its been available since v16.8.0. More about hooks here
Hooks let you always use functions instead of having to constantly switch between functions, classes, higher-order components, and render props. - Dan Abramov
Let's dive right in.
Requirements
For this tutorial, you'd need a basic understanding of React and Javascript.
Getting started
You can use create-react-app to quickly scaffold a new react app.
npx create-react-app react-timer
Building the timer
Create a Timer.js
component and style.css
file in the /src
directory.
import React from 'react';
import './style';
const Timer = () => {
return (
<div className="container">
<div className="time">
<span className="minute">00</span>
<span>:</span>
<span className="second">00</span>
</div>
<div className="buttons">
<button onClick={() => null} className="start">Start</button>
<button onClick={() => null} className="reset">Reset</button>
</div>
</div>
)
}
export default Timer;
This is the barebones Timer component. It has the minute and second values which are hardcoded for now, and two buttons to start and reset the timer.
Styles
Add these styles to style.css
file to make the Timer visually appealing. 😎
.container {
width: 600px;
margin: 0 auto;
display: grid;
place-items: center;
margin-top: 5rem;
background: rgb(66,4,53);
background: linear-gradient(90deg, rgba(66,4,53,1) 0%, rgba(81,22,111,1) 35%, rgba(12,29,84,1) 100%);
padding: 3rem 5rem;
border-radius: 10px;
}
.time {
font-size: 8rem;
margin-bottom: 1rem;
color: white;
}
.buttons button {
padding: 0.8rem 2rem;
border: none;
margin-left: 0.2rem;
font-size: 1rem;
cursor: pointer;
border-radius: 5px;
font-weight: bold;
transition: all 300ms ease-in-out;
transform: translateY(0);
}
.buttons button:hover {
transform: translateY(-2px);
}
.start {
background: #3ed927;
color: white;
}
.pause {
background: #e4e446;
}
.reset {
background: #fd7171;
color: white;
}
Managing State with useState
We make our Timer a stateful component by using useState
.
import React, { useState } from 'react';
const [second, setSecond] = useState('00');
const [minute, setMinute] = useState('00');
const [isActive, setIsActive] = useState(false);
const [counter, setCounter] = useState(0);
const Timer = () => {
return (
<div className="container">
<div className="time">
<span className="minute">{minute}</span>
<span>:</span>
<span className="second">{second}</span>
</div>
<div className="buttons">
<button onClick={() => setActive(true)} className="start">Start</button>
<button onClick={() => null} className="reset">Reset</button>
</div>
</div>
)
}
We now have the second
, minute
, isActive
and counter
values in state. isActive
will be used to toggle the active and inactive states of the timer. We start the timer by adding an onClick handler to the start button which sets the isActive
state to true.
Adding effects with useEffect
To trigger the timer which is a side effect, we need to use useEffect
.
import React, { useState, useEffect } from 'react';
const [second, setSecond] = useState('00');
const [minute, setMinute] = useState('00');
const [isActive, setIsActive] = useState(false);
const [counter, setCounter] = useState(0);
const Timer = () => {
useEffect(() => {
let intervalId;
if (isActive) {
intervalId = setInterval(() => {
const secondCounter = counter % 60;
const minuteCounter = Math.floor(counter / 60);
const computedSecond = String(secondCounter).length === 1 ? `0${secondCounter}`: secondCounter;
const computedMinute = String(minuteCounter).length === 1 ? `0${minuteCounter}`: minuteCounter;
setSecond(computedSecond);
setMinute(computedMinute);
setCounter(counter => counter + 1);
}, 1000)
}
return () => clearInterval(intervalId);
}, [isActive, counter])
return (
<div className="container">
<div className="time">
<span className="minute">{minute}</span>
<span>:</span>
<span className="second">{second}</span>
</div>
<div className="buttons">
<button onClick={() => setIsActive(!isActive)} className="start">
{isActive ? "Pause": "Start"}
</button>
<button onClick={() => null} className="reset">Reset</button>
</div>
</div>
)
}
Lets break down what's going on in the useEffect
.
- Toggle the start button value(Start or Pause) based on
isActive
state. - We only run the
setInterval
function ifisActive
is true. -
secondCounter
is calculated by getting the remainder of counter divided by 60 - using the modulo operator (%). -
minuteCounter
is calculated by dividing the counter by 60 and rounding it down usingMath.floor
. - We append an extra zero to the second and minute values so that we always have 2 digits.
- We update the
second
andminute
states using thecomputedMinute
andcomputedSecond
values. -
count
is also increased by 1 every second the effect runs. - We return a cleanup function to clear the interval when the effect stops running.
- Lastly, we add the
isActive
andcounter
state to the dependency array. This ensures that the effect only runs when either of them changes.
To stop the timer and reset all state values, we add a stopTimer
function which runs when the reset button is clicked.
import React, { useState, useEffect } from 'react';
// state values are here ......
// useEffect runs here .......
const Timer = () => {
function stopTimer() {
setIsActive(false);
setCounter(0);
setSecond('00');
setMinute('00')
}
return (
<div className="container">
<div className="time">
<span className="minute">{minute}</span>
<span>:</span>
<span className="second">{second}</span>
</div>
<div className="buttons">
<button onClick={() => setIsActive(!isActive)} className="start">
{isActive ? "Pause": "Start"}
</button>
<button onClick={stopTimer} className="reset">Reset</button>
</div>
</div>
)
}
Conclusion
In this article, we learnt how to build a timer using React Hooks. The preview of the finished timer is below. Please like and share.
Top comments (11)
I think you can better optimize your code, for example by avoiding multiple re-rendering.
But nice tutorial ! Just don't forget performances!
To be superfluous and point out that performance doesn't matter unless it matters (aka don't optimize before you measure and decide it's a problem based on specs and common sense), in a real world app I'd group seconds and minutes like you did, but keep the counter out and suffer the little performance penalty each second while making everything more readable and naturally fitting the model most of us have in mind when we think about time (seconds, minutes).
Yeah. I tried to avoid making hasty performance optimizations but that's definitely something to consider in a real-world app.
Thanks for the feedback.
Set state in React will called asynchronously one time, so that this optimization doesn't need at all.
Just console.log, you will see 3 log, because of 3 updates state ;)
Here, we will have only 1 rerender
I checked useState behavoiur and I have 1 render. See example codesandbox.io/s/sweet-star-er91q?...
It's because it comes from an event, like a click on a button.
React currently will batch state updates if they're triggered from within a React-based event, like a button click or input change. It will not batch updates if they're triggered outside of a React event handler, like an async call.
Yea, you're right. I didn't know it. Thanks. But what's reason for work batching only in event handlers?
It will not corectlly work on not active tab =(
somebody knows why have a delay when start the timer and how eliminate it ??