DEV Community

Cover image for Build a Timer with React Hooks
Adesile Emmanuel
Adesile Emmanuel

Posted on • Edited on

Build a Timer with React Hooks

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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 if isActive 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 using Math.floor.
  • We append an extra zero to the second and minute values so that we always have 2 digits.
  • We update the second and minute states using the computedMinute and computedSecond 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 and counter 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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
viclafouch profile image
Victor de la Fouchardière • Edited

I think you can better optimize your code, for example by avoiding multiple re-rendering.

test

But nice tutorial ! Just don't forget performances!

Collapse
 
rad_val_ profile image
Valentin Radu

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).

Collapse
 
emmaadesile profile image
Adesile Emmanuel

Yeah. I tried to avoid making hasty performance optimizations but that's definitely something to consider in a real-world app.

Collapse
 
emmaadesile profile image
Adesile Emmanuel

Thanks for the feedback.

Collapse
 
atellmer profile image
AlexPlex

Set state in React will called asynchronously one time, so that this optimization doesn't need at all.

Collapse
 
viclafouch profile image
Victor de la Fouchardière

Just console.log, you will see 3 log, because of 3 updates state ;)

Here, we will have only 1 rerender

Thread Thread
 
atellmer profile image
AlexPlex

I checked useState behavoiur and I have 1 render. See example codesandbox.io/s/sweet-star-er91q?...

Thread Thread
 
viclafouch profile image
Victor de la Fouchardière • Edited

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.

Thread Thread
 
atellmer profile image
AlexPlex

Yea, you're right. I didn't know it. Thanks. But what's reason for work batching only in event handlers?

Collapse
 
sunks profile image
Igor

It will not corectlly work on not active tab =(

Collapse
 
franklingarcia profile image
Franklin-Garcia

somebody knows why have a delay when start the timer and how eliminate it ??