Learn react js by building a countdown timer

Kemi Owoyele - Jul 10 - - Dev Community

In this article, we are going to learn react js from scratch by building a countdown timer. We will learn
• how to create new react app,
• to create a react component,
• to dynamically display content,
• to listen to and handle events
• to use the useState hook for state management and
• to manage function side effects with the useEffect hook
Our countdown time will
• accept input from the user to set time,
• validate if the time is in the future or the past;
• start counting down if the time is valid or alert the user to set the correct time if not
• When the counter counts to zero seconds, the user will be alerted that the time is up.
Before we start building the countdown timer with react.js, I’d like to let you know some of the assumptions I’ll be working with.
• I assume that you are proficient with HTML and CSS. I will not explain the HTML/CSS codes.
• I assume that you know JavaScript, and can probably build this project in vanilla JavaScript without much trouble. But I will try as much as possible to explain the JavaScript codes.
• I assume you are relatively new to react. I’ll explain all the react concepts as though you are completely new to react js.

Create react app

The first thing you will need to do is to set up a react development environment. To do that, we will open our terminal and navigate to the location where we want to create the app and run

npx create-react-app countdown-timer
Enter fullscreen mode Exit fullscreen mode

npx will run the code on the internet and create a basic react app for us with the name “countdown-timer” or whatever name you used as an alternative.
After the app is created, navigate into the folder and open it with a text editor. In the folder, you are going to find a couple of folders and files. Some of the folders include;
• node_modules folder: stores installed packages and dependencies. This folder is to be untouched.
• public folder: this folder serves as the directory for publicly accessible assets. Initial assets in this folder include index.html, favicon.ico, index.css, logo images, manifest.json, etc. Files in the public folder do not require imports, and can be accessed directly from the root folder. Files in this folder may be deleted or tampered with, except the index.html file.
• src folder: src is short for source. It is where the source codes are kept. All your styles, logic, components, etc. are going into the src folder.
*files
*

The files include .gitignore, package.json, package-lock.json, README.md. These files are not to be tampered with. Except for the git ignore, in case you added some assets that you want git to ignore.
• index.html is the entry point to your react application. This is the file that will be returned from the server. The root component is then mounted on the index.html and the rest of the app's logic and interface will handled from there on.
• Index.js file is where the root component is mounted to reactDOM.
• App.js is the root component. A component is an autonomous segment of content, usually containing its logic and its HTML-like template. A react app is made up of a component tree with the root component at the origin point of the other components
To run the app, open your terminal, ensure it is navigated to your app folder, and type

npm start
Enter fullscreen mode Exit fullscreen mode

A localhost address will be made available to you for viewing your app.

Create the CountDown component

In the src folder, create a CountDown.js file. Remember that the name of the file should start with an upper case, as this is the standard for component file naming. In the CountDown.js file, create a basic CountDown component and export the component.
Countdown.js file

const CountDown = () => {
 return 
 (<></>);
};

export default CountDown;


Enter fullscreen mode Exit fullscreen mode

In your App.js file, delete the already existing content of the page, and create a basic App component. Then import the CountDown component and render it in your App component.
*App.js file
*

import CountDown from "./CountDown";
function App() {
  return (
    <>
      <CountDown />
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Build The Interface Of The Countdown Timer

Go back to your CountDown.js file, in the return statement of your component, you will render jsx content. jsx is HTML like but is actually javascript. You may also create an app.css file and import the file at the top of your page;

import "./app.css";
Enter fullscreen mode Exit fullscreen mode

We will create a section for selecting and displaying the target time. In this section, we will use the date time picker of the input-type date to allow the user to select the date and time for the target time. The time selected will be displayed in a div inside this section.
Another section will be used to display the time remaining in days, hours, minutes and seconds to the target time. For these figures, we will use 0 as the placeholders for now. When we get to the functionality part, we will dynamically output the figures.

HTML/jsx code

import "./app.css";
const CountDown = () => {
  return (
    <>
      <div className="container">
        <h1>Countdown Timer</h1>

        <section className="select-time">
          <div>Set Countdown Target Time</div>
          <input type="datetime-local"></input>
          <div>Countdown Target Time</div>
          <div className="time-set">0</div>
        </section>
        <section className="display-time">
          <div className="wrapper">
            <div className="time">0</div>
            <div className="label">Days</div>
          </div>

          <div className="wrapper">
            <div className="time">0</div>
            <div className="label">Hours</div>
          </div>

          <div className="wrapper">
            <div className="time">0</div>
            <div className="label">Minutes</div>
          </div>

          <div className="wrapper">
            <div className="time">0</div>
            <div className="label">Seconds</div>
          </div>
        </section>
      </div>
    </>
  );
};

export default CountDown;
Enter fullscreen mode Exit fullscreen mode

you can style this page as you please.
CSS code

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  margin: 0;
  font-family: "Segoe UI", "Roboto", sans-serif;
  background: radial-gradient(
    circle,
    rgb(190, 190, 190),
    rgb(117, 117, 123),
    rgb(95, 126, 228)
  );
  background-size: 10px 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  color: aliceblue;
}

.container {
  color: rgb(255, 255, 255);
  box-shadow: 2px 2px 10px black;
  padding: 20px;
  background: linear-gradient(
    90deg,
    rgb(64, 60, 82),
    rgb(97, 97, 110),
    rgb(126, 130, 220)
  );
  border-radius: 15px;
  text-align: center;
}
h1 {
  font-size: 3rem;
  text-shadow: 1px 1px rgb(36, 32, 61), 2px 2px 4px rgb(36, 32, 61);
}
.select-time {
  background: rgba(36, 38, 51, 0.645);
  padding: 10px;
  margin: 10px 0;
  border-radius: 10px;
  box-shadow: 0 0 5px black;
  font-size: 1.5em;
  font-weight: bold;
}
.select-time input {
  padding: 5px;
  font-size: 1em;
  color: rgb(8, 2, 79);
  background-color: rgb(199, 199, 255);
 -webkit-text-stroke: 1px rgb(198, 106, 156);
}
.time-set {
  color: rgb(143, 198, 202);
  -webkit-text-stroke: 1px rgb(198, 106, 156);
}
.wrapper {
  margin: 10px 0;
  padding: 5px 0;
  box-shadow: 0 0 5px black;
}
.time {
  font-size: 2rem;
  font-weight: 900;
  -webkit-text-stroke: 2px rgb(36, 32, 61);
  text-shadow: 1px 1px rgb(36, 32, 61), 2px 2px rgb(36, 32, 61);
}


Enter fullscreen mode Exit fullscreen mode

Appearance so far

Image description
Now that we have set up what our app will look like, we will work on the logic/ functionality of the app.

Adding functionality to the app

Before we go further into the JavaScript aspect, there are two dependencies that we have to import and briefly explain. We will import the useState hook and the useEffect hook from the react library.

import { useState, useEffect } from "react";
Enter fullscreen mode Exit fullscreen mode

Place the code at the top of your component file.

useState hook

When you change the value of a variable either in reaction to some event or over time, react will not re-render the new value. The useState hook is used to inform react that the value of a variable has changed. This will trigger a re-render of the component, and the changes can therefore be reflected in the new render.
To use the useState hook, you will declare a variable with two values (the name of the variable we want to make reactive, and the function for changing/setting the value of the variable.), and set it equal to the useState.

const [targetTime, setTargetTime] = useState();
Enter fullscreen mode Exit fullscreen mode

This code should be written in your component function, but above the jsx template.

Get user input from the date picker

The next thing we would do now is to use the useState hook to render the date and time that the user picks with the date picker or enters in the input tag. To do this, we will create a function and name it handleTimeSelected, this function will be an event listener to listen for changes in the value of the input tag. To make that value reactive and trigger a re-render, we will use the useState hook values. We will use setTargetTime to set the value changes for targetTime.

const handleTimeChange = (event) => {
      setTargetTime(event.target.value);
  };

Enter fullscreen mode Exit fullscreen mode

In our jsx, we will reference the handleTimeChange function in an onChange event handler. Also, we will set the value of the input tag to be equal to targetTime.

<input
            type="datetime-local"
            value={targetTime}
            onChange={handleTimeChange}
       >
</input>

Enter fullscreen mode Exit fullscreen mode

Dynamically output the value of the user-selected time

All we need to do here is to wrap the name of the variable we intend to dynamically output in curly braces, and place it in the jsx location we want it outputted in.

<div className="time-set"> 
{targetTime}
</div>

Enter fullscreen mode Exit fullscreen mode

This can also be used to dynamically set HTML attributes, as we did above to set the value attribute to targetTime.

Image description

As we can see, the time selected with the date and time picker is showing in another div tag below it.
Check For Time Validity
Another thing we would like to check for is to verify that the time chosen by the user is a future date and time. If the time selected is already in the past, we would trigger an alert to notify the user that the time selected is invalid and set targetTime to “invalid”.
Back into the handleTimeChange function.
First we need to get the current time, then compare the current time with the selected time. To get current time, we will use the JavaScript date method.

const currentTime = new Date();
    const selectedTime = new Date(event.target.value);


Enter fullscreen mode Exit fullscreen mode

Next, we will use a conditional statement to compare the values.

const handleTimeChange = (event) => {
    const currentTime = new Date();
    const selectedTime = new Date(event.target.value);
    if (selectedTime >= currentTime) {
      setTargetTime(event.target.value);
    } else {
      setTargetTime("Invalid Time");
      alert("invalid time; please select a future date and time");
    }
  };


Enter fullscreen mode Exit fullscreen mode

Image description
Get the time remaining to the target time?
Now we are going to get the number of days, hours, minutes and seconds between the target time and the current time. This can be derived by deducting the current time from the target time. The number we are going to get will be the total milliseconds between those two periods. To get the other time durations, we will h divide the number appropriately.

  const getTargetTime = new Date(targetTime);
  const getCountDownTime = () => {
    const totalTimeLeft = getTargetTime - new Date();
    const daysLeft = Math.floor(totalTimeLeft / (1000 * 60 * 60 * 24));
    const hoursLeft = Math.floor((totalTimeLeft / (1000 * 60 * 60)) % 24);
    const minutesLeft = Math.floor((totalTimeLeft / (1000 * 60)) % 60);
    const secondsLeft = Math.floor((totalTimeLeft / 1000) % 60);
    return [daysLeft, hoursLeft, minutesLeft, secondsLeft];
  };

Enter fullscreen mode Exit fullscreen mode

To get the total number of days left, we find the total milliseconds remaining, and divided the answer by 24 hours, multiplied by 60 minutes, 60 seconds and 1000 milliseconds. Then the function returned an array of the needed values.
Dynamically display the remaining time
Now we want to take the returned values from the getCountDownTime() function and display them appropriately in the jsx render. getCountDownTime() returns an array, in our jsx, we will display the corresponding index of the getCountDownTime() array.

<div className="wrapper">
            <div className="time">{getCountDownTime()[0]}</div>
            <div className="label">Days</div>
          </div>

          <div className="wrapper">
            <div className="time">{getCountDownTime()[1]}</div>
            <div className="label">Hours</div>
          </div>

          <div className="wrapper">
            <div className="time">{getCountDownTime()[2]}</div>
            <div className="label">Minutes</div>
          </div>

          <div className="wrapper">
            <div className="time">{getCountDownTime()[3]}</div>
            <div className="label">Seconds</div>
          </div>


Enter fullscreen mode Exit fullscreen mode

Output

Image description
Set the countdown
The next thing we are going to do is to use setInterval to decrease the remaining time every second. We also want to ensure that the timer stops counting as soon as the time left is 0 seconds and the user is notified that time is up.
If you are familiar with JavaScript you probably know how to use the setInterval method. It is used the same way in react, but before setting up the setInterval, I want us to briefly explain the useEffect hook.

useEffect hook

The useEfect hook is used to handle function side effect in react. Function side effect in JavaScript is any effect outside the function’s local scope. If the function produces a result that is not part of the functions return value. Examples of function side effect include;
• Changing a global variable
• Interacting with the DOM
• Mutating external arrays, objects etc.
• Fetching data from external API’s
• Javascript animations
• Set timers
• Using web storage
• Etc.
React js is built on the concept of pure functions. Pure functions are functions that have no side effects. They always produce the same results if given the same inputs. This is because functions with side effects are quite unpredictable in behavior, harder to debug, test and maintain. There are also performance issues and memory leaks with regards to unnecessary re-renders or computation.
To handle functions with side effects, such as timers, api requests, updating UI etc we make use of useEffect hook.

How to use useEffect hook

First of all, we need to;
• Import useEffect from react
• Somewhere in your component, before the return statement, call the useEffect hook with two arguments.

• The first argument is a callback function containing instructions we want to execute whenever the useEffect is triggered.

• The second optional argument is an array of dependencies that will determine when the useEffect will run. If the array is empty, the useEffect will run only once after the initial render. If the dependency argument is omitted, the useEffect will trigger whenever there is a state change. If values are provided inside the dependency array, the useEffect will be triggered only when there is a state change in any of those values.
• To avoid memory leaks and other performance issues, it is important to clean-up resources when they are no longer needed. This is usually implemented with a cleanup function.
• Syntax

 useEffect(()=>{
    // excecute side effect
//cleanup function
  }, [an array of values that the effect depends on]);

Enter fullscreen mode Exit fullscreen mode

Back to our countdown timer
First set up the useState for the timer that we intend to update

  const [timer, setTimer] = useState();
Enter fullscreen mode Exit fullscreen mode

Use the useEffect hook to execute the setIntervals

useEffect(() => {
    const total = getTargetTime - new Date();

    const countDownInterval = setInterval(() => {
      if (total >= 0) {
        setTimer(getCountDownTime());
      } else {
        clearInterval(countDownInterval);
        alert(`Time up
           Set New Time`);
      }
    }, 1000);


Enter fullscreen mode Exit fullscreen mode

At the stage the entire code in the countdown.js page will be

import "./app.css";
import { useState, useEffect } from "react";

const CountDown = () => {
  const [targetTime, setTargetTime] = useState();
  const [timer, setTimer] = useState();

  const handleTimeChange = (event) => {
    const currentTime = new Date();
    const selectedTime = new Date(event.target.value);
    if (selectedTime >= currentTime) {
      setTargetTime(event.target.value);
    } else {
      setTargetTime("Invalid Time");
      alert("invalid time; please select a future date and time");
    }
  };
  const getTargetTime = new Date(targetTime);
  const getCountDownTime = () => {
    const totalTimeLeft = getTargetTime - new Date();
    const daysLeft = Math.floor(totalTimeLeft / (1000 * 60 * 60 * 24));
    const hoursLeft = Math.floor((totalTimeLeft / (1000 * 60 * 60)) % 24);
    const minutesLeft = Math.floor((totalTimeLeft / (1000 * 60)) % 60);
    const secondsLeft = Math.floor((totalTimeLeft / 1000) % 60);
    return [daysLeft, hoursLeft, minutesLeft, secondsLeft];
  };
  useEffect(() => {
    const total = getTargetTime - new Date();

    const countDownInterval = setInterval(() => {
      console.log(total);
      if (total >= 0) {
        setTimer(getCountDownTime());
      } else {
        clearInterval(countDownInterval);
        alert(`Time up
           Set New Time`);
      }
    }, 1000);

    return () => {
      clearInterval(countDownInterval);
    };
  }, [timer, getTargetTime]);

  return (
    <>
      <div className="container">
        <h1>Countdown Timer</h1>

        <section className="select-time">
          <div>Set Countdown Target Time</div>
          <input
            type="datetime-local"
            value={targetTime}
            onChange={handleTimeChange}
          ></input>
          <div>Countdown Target Time</div>
          <div className="time-set">{targetTime}</div>
        </section>
        <section className="display-time">
          <div className="wrapper">
            <div className="time">{getCountDownTime()[0]}</div>
            <div className="label">Days</div>
          </div>

          <div className="wrapper">
            <div className="time">{getCountDownTime()[1]}</div>
            <div className="label">Hours</div>
          </div>

          <div className="wrapper">
            <div className="time">{getCountDownTime()[2]}</div>
            <div className="label">Minutes</div>
          </div>

          <div className="wrapper">
            <div className="time">{getCountDownTime()[3]}</div>
            <div className="label">Seconds</div>
          </div>
        </section>
      </div>
    </>
  );
};

export default CountDown;


Enter fullscreen mode Exit fullscreen mode

Output

Image description

Conclusion

In this tutorial, we have explored the fundamentals of React JS by building a practical application - a countdown timer. We have covered key concepts such as component creation, state management, event handling, and side effect management using the useState and useEffect hooks. Our countdown timer is now fully functional, and we have gained hands-on experience by building a React application from scratch.

. . . . . . . . . . . . . . . . . . . . . . . . . . .