# Countdown timer with React

![Countdown timer design](https://cdn.hashnode.com/res/hashnode/image/upload/v1657109390230/y1xv_I4MX.png align="left")

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);
// ...
```

The 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 a reference to the `timerID` that persists across re-renders without causing a re-render of its 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 gets 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, let's 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](https://stackoverflow.com/a/1322771/1939344)

Now, let's consume it in our 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:

<iframe src="https://codesandbox.io/embed/rohan-bagchi-countdown-timer--personal-24hc37?fontsize=14&hidenavigation=1&theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
