Creating a Countdown Timer with react-hook
Tags: javascript, typescript, react, beginners
Published: 2022-03-03 13:04:49

## Intro

Here we are to talk how to create a countdown timer in react using native hooks.

This timer that we will build is a countdown of 5 minutes, but nothing will hold you, if you want to try do a stopwatch based on this material.

Using the `useEffect()` and `useState()` hooks is possible to store the data of your time and update on screen in real time, but to do that we will need the help from the function `setTimeout()`.

## Let's begin!

To start our project make shure that your project is a React project in Typescript or javascript, you can create those with create-react-app, next or vite.

If is your first try that is how you create the project:

```jsx
npx create-react-app my-app --template typescript

# or

yarn create react-app my-app --template typescript
```

## How this works?

This timer we are about to create needs a start time, in this project we will set to 5 minutes.

Now we need to think how tom make this work, the amount time could be in hours, minutes or seconds, we will use seconds just by the fact that is easier to convert seconds to minutes or hours, because all seconds will be a integer number.

## Starting the code

So the first thing that we will do is to import our hooks and stablish our start time:

```tsx
import { useEffect, useState } from "react";

const COUNTDOWN_AMOUNT_TOTAL = 5 * 60; // 5 minutes for timer
```

In this way our variable start in 5 minutes, cause every minute has 60 seconds, with clean code rules we use this equation to have the value amount in seconds.

## Timer

To start this timer we need a react function, that we'll name `Timer()`, we need this function because every hook must be inside a react function to work.

So our code is like that:

```tsx
import { useEffect, useState } from "react";

const COUNTDOWN_AMOUNT_TOTAL = 5 * 60; // 5 minutes for timer

export function Timer(){

const [seconds, setSeconds] = useState<number>(COUNTDOWN_AMOUNT_TOTAL);

}
```

Here we create our function and create our state variable for the seconds amount, and started them with our start value, the start value variable is create outside our function just to when the code runs that value will be mount just one time and not be recreated every time that the `useEffect()` changes.

How we need to update the state every second we'll use the `useEffect()` : 

```tsx
import { useEffect, useState } from "react";

const COUNTDOWN_AMOUNT_TOTAL = 5 * 60; // 5 minutes for timer

export function Timer(){

const [seconds, setSeconds] = useState<number>(COUNTDOWN_AMOUNT_TOTAL);

useEffect(()=>{
	if(seconds > 0){
		// +1 second
	} else {
		// stop
	} 
}, [seconds])

}
```

Inside our `useEffect()` we have a condition that should make our time pass while our seconds amount is more than 0, and when this amount turns into 0 it should stop the count.

Just to make the code optimized we'll include another variable outside the main function called timeout:

```tsx
import { useEffect, useState } from "react";

let timeout: NodeJS.Timeout;
const COUNTDOWN_AMOUNT_TOTAL = 5 * 60; // 5 minutes for timer

export function Timer(){

const [seconds, setSeconds] = useState<number>(COUNTDOWN_AMOUNT_TOTAL);

useEffect(()=>{
	if(seconds > 0){
		timeout = setTimeout(() => {
        setSeconds((state) => state - 1);
      }, 1000);
	} else {
		clearTimeout(timeout);
	} 
}, [seconds])

}
```
Now our timer is already working, counting precisaly every second, because in the function `setTimeout()` we set a arrow function that changes the state of seconds every 1000 miliseconds (1 second), this will happen every time that seconds is changed, because we passed seconds as params for the `useEffect(() => {}, [seconds])`, when it comes to 0 with the `clearTimeout()` the timer will stop.

Just because i'm usign typescript i needed to declare the types of every varibale.

## Refining

After create the timer we see that it is only a countdown in seconds, and it is never shown in screen, how can we show this in screen and separate minutes from seconds?

If every minute has 60 seconds we just need of 2 equations:

```tsx
import { useEffect, useState } from "react";

let timeout: NodeJS.Timeout;
const COUNTDOWN_AMOUNT_TOTAL = 5 * 60; // 5 minutes for timer

export function Timer(){

const [seconds, setSeconds] = useState<number>(COUNTDOWN_AMOUNT_TOTAL);

const displaySeconds = seconds % 60;
const displayMinutes = Math.floor(seconds / 60);

useEffect(()=>{
	if(seconds > 0){
		timeout = setTimeout(() => {
        setSeconds((state) => state - 1);
      }, 1000);
	} else {
		clearTimeout(timeout);
	} 
}, [seconds])

}
```
To show the minutes we need to divide the seconds by 60 and because this division can be a uneven number we need to use the function `Math.floor()` that returns only the number before the comma.

To show the seconds we need the rest of this dvision, and to take the rest we need the operator %, the rest of this division is the seconds in that specific minute.

Now is possible to take this varibles and put in the screen:

```tsx
import { useEffect, useState } from "react";

let timeout: NodeJS.Timeout;
const COUNTDOWN_AMOUNT_TOTAL = 5 * 60; // 5 minutes for timer

export function Timer(){

const [seconds, setSeconds] = useState<number>(COUNTDOWN_AMOUNT_TOTAL);

	const displaySeconds = seconds % 60;
	const displayMinutes = Math.floor(seconds / 60);
	
	useEffect(()=>{
		if(seconds > 0){
			timeout = setTimeout(() => {
	        setSeconds((state) => state - 1);
	      }, 1000);
		} else {
			clearTimeout(timeout);
		} 
	}, [seconds]);

	return(
		<>
			<h1>{displayMinutes}</h1>
			<p>:</p>
			<h1>{displaySeconds}</h1>
		</>
	)

}
```

## Conclusion

- We learn the concept to create a timer;
- We saw how we can use `useState()` and `useEffect()` hooks to update the data.
- Learned about `setTimeout()` to pass the time with accuracy.
- And finally we build a countdown timer.

Hope this have helped you and do not be limited by only what's written in here, be creative and use this to expand your knowledge.