Countdown timer with React
Tags: react, javascript, timer
Published: 2022-09-05 08:38:25


![Countdown timer with React](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2b959cdlc319uk7o8xpi.jpg)

Navigating the quirks of React's state and component model to build a countdown timer you can start, pause, resume, stop and reset. Idea is to start an interval and keep track of it's reference in a ref. On pausing said timer, we can clear the interval same way we can start a new timer on resume.

---

The ask goes like this:

Allow user to enter time in seconds. On click of `Start`, existing timer if any must be cancelled and it should begin countdown from the newly entered time.

To start with, lets split our work into 2 parts:

1. Counting down seconds
2. Formatting seconds into hh:mm:ss

### Counting down seconds

Let's add a form with an input and a submit button.

```js
<form onSubmit={handleStart}>
    <input
        type="number"
        placeholder="Enter total seconds to countdown"
        ref={inputRef}
    />

    <button type="submit">Start</button>
</form>
```

This will give us a basic form with an uncontrolled field (which is fine as we do not really need a state on user enterred value).
There is also `inputRef` which is a vanilla React ref. Lets define it.

```js
import { useRef } from "react";

export default function App() {
    const inputRef = useRef();
    
    return (
        <form onSubmit={handleStart}>
            <input
                type="number"
                placeholder="Enter total seconds to countdown"
                ref={inputRef}
            />
    
            <button type="submit">Start</button>
        </form>
    )
}
```

Now lets consider the timer. The way it works is by decrementing a count until it reaches `0`.
The only way to change a value and have it automatically updated in the DOM in React is by using state.

Let's add it below our `inputRef` declaration.

```js
// ...
const inputRef = useRef();
const [currentTime, setCurrentTime] = useState(0);
// ...
```

Next step is the actual countdown. We can make use of `setInterval` method to execute a decrement after every second. Along with this, we need to clear a running timer before the component unmounts.

Also, as per the ask, every time our form is submitted, we need to reset the timer. So we need reference to the timerID that persists accross re-renders without causing a re-render of it's own. To that effect, we can use a `ref`.

```js
// ...
const inputRef = useRef();
const timerRef = useRef();
const [currentTime, setCurrentTime] = useState(0);

useEffect(() => {
    return () => {
        clearInterval(timerRef.current);
    };
}, []);

const startTimer = () => {
    timerRef.current = setInterval(() => {
        setCurrentTime((prev) => prev - 1);
    }, 1000);
}; 
// ...
```

The `useEffect` here returns a cleanup function that executes on unmount. Here we are clearing up any running timer.

The `startTimer` function is responsible for starting a timer with a fixed delay of `1000` milliseconds and decrementing `currentTime` by 1 on every tick.

Let's bring it all together in our `handleStart` function which get's triggered on form submit.

Things to note:

1. `handleStart` will clear any existing timers
2. set value of `currentTime`
3. trigger `startTimer`

```js
// ...
const handleStart = e => {
    e.preventDefault();

    if (timerRef.current) {
      clearInterval(timerRef.current);
    }

    const secondsInput = inputRef.current.value;

    setCurrentTime(() => secondsInput);

    startTimer();
};
// ...
```

Here, `const secondsInput = inputRef.current.value;` gets the current value of the input.

### Formatting seconds into hh:mm:ss

Now that we have a decrementing counter, lets also add a utility to format it to `hh:mm:ss`.

```js
const secondsToHHMMSS = (seconds) => {
  if (seconds < 3600)
    return new Date(seconds * 1000).toISOString().substr(14, 5);

  return new Date(seconds * 1000).toISOString().substr(11, 8);
};
```

^ is picked from this helpful stackoverflow answer: <https://stackoverflow.com/a/1322771/1939344>

Now, lets render it below the form:

```js
// ...
return (
    <div className="App">
        <form onSubmit={handleStart}>
            <input
            type="number"
            placeholder="Enter total seconds to countdown"
            ref={inputRef}
            />

            <button type="submit">Start</button>
        </form>

        <div className="time">{secondsToHHMMSS(currentTime)}</div>
    </div>
);
// ...
```

Finished result:
{% codesandbox https://codesandbox.io/embed/rohan-bagchi-countdown-timer--personal-24hc37?fontsize=14&hidenavigation=1&theme=dark %}