How to develop a Stopwatch in React JS with custom hook
Tags: react, beginners, javascript, tutorial
Published: 2020-07-19 16:09:49

---
title: How to develop a Stopwatch in React JS with custom hook 
published: true
description: 
tags: react, beginners, javascript, tutorial
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/cnbsyb3wg0k9wq5fhyd5.png
---
In order to truly understand how things work we should break down the task into smaller pieces, this is what we are doing here. Our ultimate goal is to build a **React Native** **Pomodoro clock App** but first, we will build a *stopwatch* to understand how `setInterval` and `clearInterval` works in react with hooks then turn this *stopwatch* into a Pomodoro clock, and so on.

### **Let's start**

Let's break down everything and build a boilerplate first.

```jsx
import React, { useState } from 'react';
import './App.css';

const App = () => {
  const [timer, setTimer] = useState(0)
  const [isActive, setIsActive] = useState(false)
  const [isPaused, setIsPaused] = useState(false)
  const countRef = useRef(null)

  const handleStart = () => {
    // start button logic here
  }

  const handlePause = () => {
    // Pause button logic here
  }

  const handleResume = () => {
    // Resume button logic here
  }

  const handleReset = () => {
    // Reset button logic here
  }

  return (
    <div className="app">
      <h3>React Stopwatch</h3>
      <div className='stopwatch-card'>
        <p>{timer}</p> {/* here we will show timer */}
        <div className='buttons'>
          <button onClick={handleStart}>Start</button>
          <button onClick={handlePause}>Pause</button>
          <button onClick={handleResume}>Resume</button>
          <button onClick={handleReset}>Reset</button>
        </div>
      </div>
    </div>
  );
}

export default App;
```

A timer will start from 0 to onward by clicking the start button.

`isActive` is defined to see if the timer is active or not.

`isPaused` is defined to see if the timer is paused or not.

Initially, both values will be `false`. We have defined these values to conditionally render Start, Pause, and Resume button.

### UseRef hook

`useRef` helps us to get or control any element's reference.

It is the same as we get the reference in vanilla javascript by using `document.getElementById("demo")` which means we have skipped virtual dom and directly dealing with browsers. Isn't the `useRef` hook is powerful?

If we run this code we will see the result like this. (CSS is included at the end of the article)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mlzp7x2tvqk9afpml7ap.PNG)

Now we have three tasks to do, 

- to write a function for each button
- format the timer the way we see in stopwatch (00: 00: 00)
- conditionally rendering the buttons

### Start Function

The job of start function is to start the timer and keep incrementing until we reset or pause it.

For that, we will use `setInterval` method. `setInterval` runs as long as we don't stop it. It takes two parameters. A *callback* and *time in milliseconds*.

`setInterval(func, time)`

`1000 ms = 1 second`

```jsx
const handleStart = () => {
  setIsActive(true)
  setIsPaused(true)
  countRef.current = setInterval(() => {
    setTimer((timer) => timer + 1)
  }, 1000)
}
```

As soon we will hit the start button, `isActive` and `isPaused` will become `true` and 1 will be added to timer values every second.

We set `countRef` current property to the setInterval function, which means we set the timerId in variable `countRef`, now we can use it in other functions.

We used `countRef.current` to get the current value of the reference.

### Pause Function

`setInterval` keeps calling itself until `clearInterval` is called. 

In order to *stop* or *pause* the *counter* we need to use `clearInterval` function. clearInterval needs one parameter that is *id*. We will pass `countRef.current` as arguement in `clearInterval` method.

```jsx
const handlePause = () => {
  clearInterval(countRef.current)
  setIsPaused(false)
}
```

on pressing Pause button we will stop (not reset) the timer, and change `isPaused` state from `true` to `false`.

### Resume Function

```jsx
const handleResume = () => {
  setIsPaused(true)
  countRef.current = setInterval(() => {
    setTimer((timer) => timer + 1)
  }, 1000)
}
```

On resuming the timer we will start the timer from where it was paused and change `isPaused` from `false` to `true`.

### Reset Function

```jsx
const handleReset = () => {
  clearInterval(countRef.current)
  setIsActive(false)
  setIsPaused(false)
  setTimer(0)
}
```

Reset function will reset everything to its initial values. This button will not only stop the counter but also reset its value to 0.

### Rendering Buttons Logic

Let's talk about start, pause, and resume button rendering logic.

Once the timer starts, the `start button` will change into `Pause`, if we pause the timer we will see `Resume button`. This is how stopwatches work or you may say how we want this to work.

*How do we know which button to show?*

For that, we have already defined two keys in our state. One `isActive`, and other one is `isPaused`

And both of them will be false initially.

*If both keys will be false, we will show start button. It's obvious.*

**What happens in case of pause?**

isActive will be true, isPaused will be false

*Otherwise we will show resume button*

We need to write nested `if else` condition. Either we show start or pause/resume button.

### Formatting Timer

Another tricky part of app is to show the timer in this manner `00:00:00`

**For seconds** 

```jsx
const getSeconds = `0${(timer % 60)}`.slice(-2)
```

**For minutes**

```jsx
 const minutes = `${Math.floor(timer / 60)}`
 const getMinutes = `0${minutes % 60}`.slice(-2)
```

**For hours**

```jsx
const getHours = `0${Math.floor(timer / 3600)}`.slice(-2)
```

We made the `formatTime` function for this, which returns seconds, minutes, and hours. 

```jsx
  const formatTime = () => {
    const getSeconds = `0${(timer % 60)}`.slice(-2)
    const minutes = `${Math.floor(timer / 60)}`
    const getMinutes = `0${minutes % 60}`.slice(-2)
    const getHours = `0${Math.floor(timer / 3600)}`.slice(-2)

    return `${getHours} : ${getMinutes} : ${getSeconds}`
  }
```

In react the button has `disabled` props that is false by default we can make it true by adding some logic. We made reset button disabled if the timer is set to 0 just by adding simple logic `disabled={!isActive}` 

### So far Complete Code

```jsx
import React, { useState, useRef } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faClock } from '@fortawesome/free-regular-svg-icons'

import './App.css';

const element = <FontAwesomeIcon icon={faClock} />

const App = () => {
  const [timer, setTimer] = useState(3595)
  const [isActive, setIsActive] = useState(false)
  const [isPaused, setIsPaused] = useState(false)
  const increment = useRef(null)

  const handleStart = () => {
    setIsActive(true)
    setIsPaused(true)
    increment.current = setInterval(() => {
      setTimer((timer) => timer + 1)
    }, 1000)
  }

  const handlePause = () => {
    clearInterval(increment.current)
    setIsPaused(false)
  }

  const handleResume = () => {
    setIsPaused(true)
    increment.current = setInterval(() => {
      setTimer((timer) => timer + 1)
    }, 1000)
  }

  const handleReset = () => {
    clearInterval(increment.current)
    setIsActive(false)
    setIsPaused(false)
    setTimer(0)
  }

  const formatTime = () => {
    const getSeconds = `0${(timer % 60)}`.slice(-2)
    const minutes = `${Math.floor(timer / 60)}`
    const getMinutes = `0${minutes % 60}`.slice(-2)
    const getHours = `0${Math.floor(timer / 3600)}`.slice(-2)

    return `${getHours} : ${getMinutes} : ${getSeconds}`
  }

  return (
    <div className="app">
      <h3>React Stopwatch {element}</h3>
      <div className='stopwatch-card'>
        <p>{formatTime()}</p>
        <div className='buttons'>
          {
            !isActive && !isPaused ?
              <button onClick={handleStart}>Start</button>
              : (
                isPaused ? <button onClick={handlePause}>Pause</button> :
                  <button onClick={handleResume}>Resume</button>
              )
          }
          <button onClick={handleReset} disabled={!isActive}>Reset</button>
        </div>
      </div>
    </div>
  );
}

export default App;
```

### Let's clean up our code

I have realized we can extract our state and methods to a custom hook. This will make our code clean and reusable.

### useTimer hook

In `src` folder, I have created one more folder `hook` and within hook I created a file `useTimer.js`

useTimer hook returns our state and all four functions. Now we can use it wherever we want in our app.

```jsx
import { useState, useRef } from 'react';

const useTimer = (initialState = 0) => {
  const [timer, setTimer] = useState(initialState)
  const [isActive, setIsActive] = useState(false)
  const [isPaused, setIsPaused] = useState(false)
  const countRef = useRef(null)

  const handleStart = () => {
    setIsActive(true)
    setIsPaused(true)
    countRef.current = setInterval(() => {
      setTimer((timer) => timer + 1)
    }, 1000)
  }

  const handlePause = () => {
    clearInterval(countRef.current)
    setIsPaused(false)
  }

  const handleResume = () => {
    setIsPaused(true)
    countRef.current = setInterval(() => {
      setTimer((timer) => timer + 1)
    }, 1000)
  }

  const handleReset = () => {
    clearInterval(countRef.current)
    setIsActive(false)
    setIsPaused(false)
    setTimer(0)
  }

  return { timer, isActive, isPaused, handleStart, handlePause, handleResume, handleReset }
}

export default useTimer
```

### utils
We can make our code cleaner by writing our vanilla javascript functions into utils folder.

For that, within `src` I created `utils` folder, and inside utils I created `index.js` file.

```jsx
export const formatTime = (timer) => {
  const getSeconds = `0${(timer % 60)}`.slice(-2)
  const minutes = `${Math.floor(timer / 60)}`
  const getMinutes = `0${minutes % 60}`.slice(-2)
  const getHours = `0${Math.floor(timer / 3600)}`.slice(-2)

  return `${getHours} : ${getMinutes} : ${getSeconds}`
}
```

### Timer.js
I copied code from `App.js` to `Timer.js` and render `Timer.js` inside `App.js`

This is how our folder structure will look like
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/iyfeaow2c5uymtnhid65.PNG)

```jsx
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faClock } from '@fortawesome/free-regular-svg-icons'

import useTimer from '../hooks/useTimer';
import { formatTime } from '../utils';

const element = <FontAwesomeIcon icon={faClock} />

const Timer = () => {
  const { timer, isActive, isPaused, handleStart, handlePause, handleResume, handleReset } = useTimer(0)

  return (
    <div className="app">
      <h3>React Stopwatch {element}</h3>
      <div className='stopwatch-card'>
        <p>{formatTime(timer)}</p>
        <div className='buttons'>
          {
            !isActive && !isPaused ?
              <button onClick={handleStart}>Start</button>
              : (
                isPaused ? <button onClick={handlePause}>Pause</button> :
                  <button onClick={handleResume}>Resume</button>
              )
          }
          <button onClick={handleReset} disabled={!isActive}>Reset</button>
        </div>
      </div>
    </div>
  );
}

export default Timer;
```

Doesn't it look cleaner now?

**CSS**
```css
@import url("https://fonts.googleapis.com/css2?family=Quicksand:wght@500&display=swap");

body {
  margin: 0;
  font-family: "Quicksand", sans-serif;
  background-color: #eceff1;
  color: #010b40;
}

.app {
  background-color: #0e4d92;
  margin: 0 auto;
  width: 300px;
  height: 200px;
  position: relative;
  border-radius: 10px;
}

h3 {
  color: white;
  text-align: center;
  padding-top: 8px;
  letter-spacing: 1.2px;
  font-weight: 500;
}

p {
  font-size: 28px;
}

.stopwatch-card {
  position: absolute;
  text-align: center;
  background-color: white;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
  width: 325px;
  height: 130px;
  top: 110px;
  left: 50%;
  transform: translate(-50%, -50%);
}

button {
  outline: none;
  background: transparent;
  border: 1px solid blue;
  padding: 5px 10px;
  border-radius: 7px;
  color: blue;
  cursor: pointer;
}

.buttons {
  display: flex;
  justify-content: space-evenly;
  width: 150px;
  margin: 0 auto;
  margin-top: 5px;
}
```

I want a little feedback if you would like to read the next article with typescript? 

Since typescript is evolving and startups are preferring those who can type javascript with typescript.

In next part, we will transform this app to the ***Pomodoro clock***.

*Codepen Demo*

{% codepen https://codepen.io/Basit600/pen/YzwRaRp %}