DEV Community

Jarod Peachey
Jarod Peachey

Posted on

Creating a Countdown using React Hooks

React Hooks are an amazing way of managing state, context, refs and just about everything else in React. They're very versatile, and if you use them right, you can really power-up your website.

In this tutorial, we're going to create a basic countdown using two hooks: useState and useEffect.

React Hooks

The two hooks we're using are two of the most used React hooks. At least, I use them more than I do the others, so I'm assuming the same can be said about other developers.

Maybe not.

Anyway, here's what each of the hooks does:

The useState Hook

The useState hook is the equivalent to the state object in React class-based components. It manages the state, and allows you to update that state object.

The hook takes in two defenitions: the name of the state item, and the name of the function that updates that state item.

The simplest implmentation of this hook is creating a single state object

const [state, setState] = useState({});

However, you can also create a seperate state item for everything you want

const [valueOne, setValueOne] = useState(1);
const [valueTwo, setValueTwo] = useState(2);

We'll be using both methods in our countdown component.

The useEffect Hook

The useEffect hook is, in a way, the jack-of-all-trades hook in React. You can use it to update state if something happens, trigger a re-render based off of a state value, or any other number of things.

The basic implementation of this hook is:

useEffect(() => {
    // Code in here
}, [])

The useEffect hook takes 2 parameters: the callback function and the value to watch.

The second argument can be either an empty array, or a particular value. If it's an empty array, it runs the callback function once. If it's got a value in it, like this:

useEffect(() => {
    // Code in here
}, [value])

It will run whenever value changes.

Creating the Countdown

OK. Now that we've got a basic understanding of the hooks we'll be using, we can start creating the basic component layout.

First, we'll create a new file called countdown.js. Inside that file, we'll create the functional component.

const {useState, useEffect} = React;

const Countdown = () => {
  const [countdownDate, setCountdownDate] = useState(new Date('12/25/2020').getTime());
  const [state, setState] = useState({
    days: 0,
    hours: 0,
    minutes: 0,
    seconds: 0,
  });

  useEffect(() => {
    setInterval(() => updateCountdown(), 1000);
  }, []);

  const updateCountdown = () => {
    // TODO: Code to calculate how long between the countdown date and the current time
  };

  return (
    <div>
      <div className='countdown-wrapper'>
        <div className='time-section'>
          <div className='time'>{state.days || '0'}</div>
          <small className="time-text">Days</small>
        </div>
        <div className='time-section'>
          <div className='time'>:</div>
        </div>
        <div className='time-section'>
          <div className='time'>{state.hours || '00'}</div>
          <small className="time-text">Hours</small>
        </div>
        <div className='time-section'>
          <div className='time'>:</div>
        </div>
        <div className='time-section'>
          <div className='time'>{state.minutes || '00'}</div>
          <small className="time-text">Minutes</small>
        </div>
        <div className='time-section'>
          <div className='time'>:</div>
        </div>
        <div className='time-section'>
          <div className='time'>{state.seconds || '00'}</div>
          <small className="time-text">Seconds</small>
        </div>
      </div>
    </div>
  );
};

export default Countdown;

OK. So what's going on here?

The first thing we do inside our new component is create new state values using the useState hook.

const [countdownDate, setCountdownDate] = useState(new Date('12/25/2020').getTime());
const [state, setState] = useState({
  days: 0,
  hours: 0,
  minutes: 0,
  seconds: 0,
});

The first hook creates the countdown date, which I have set to Christmas.

The second hook stores our data for the remaining days, hours, minutes and seconds until the countdown date (again, Christmas). These are each set to 0, and will be updated every second.

Which brings us to the useEffect hook.

useEffect(() => {
  setInterval(() => setNewTime(), 1000);
}, []);

Inside the callback function, we're setting up an interval that will run every second. Each time it runs, it calls our updateCountdown function (which we haven't created yet. We'll get to that).

The rest of the component is the "html" for the countdown. The main thing to note is where we access the state value for days, hours, minutes and seconds.

<div className='time'>{state.hours || '00'}</div>

Updating the Countdown

The final thing to do is add the logic that updates the countdown inside the updateCountdown function.

const updateCountdown = () => {
  if (countdownDate) {
    // Get the current time
    const currentTime = new Date().getTime();

    // Get the time remaining until the countdown date
    const distanceToDate = countdownDate - currentTime;

    // Calculate days, hours, minutes and seconds remaining
    let days = Math.floor(distanceToDate / (1000 * 60 * 60 * 24));
    let hours = Math.floor(
      (distanceToDate % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
    );
    let minutes = Math.floor(
      (distanceToDate % (1000 * 60 * 60)) / (1000 * 60),
    );
    let seconds = Math.floor((distanceToDate % (1000 * 60)) / 1000);

    // For visual appeal, add a zero to each number that's only one digit
    const numbersToAddZeroTo = [1, 2, 3, 4, 5, 6, 7, 8, 9];

    if (numbersToAddZeroTo.includes(hours)) {
      hours = `0${hours}`;
    } else if (numbersToAddZeroTo.includes(minutes)) {
      minutes = `0${minutes}`;
    } else if (numbersToAddZeroTo.includes(seconds)) {
      seconds = `0${seconds}`;
    }

    // Set the state to each new time
    setState({ days: days, hours: hours, minutes, seconds });
  }
}

Basically, we're accessing the new time and subtracting that from the countdown date.

    // Get the current time
    const currentTime = new Date().getTime();

    // Get the time remaining until the countdown date
    const distanceToDate = countdownDate - currentTime;

This gives us the time remaining, and we do some fancy math stuff to calculate the days and hours left.

Fancy math stuff --------------------------------------------------- 👇

let days = Math.floor(distanceToDate / (1000 * 60 * 60 * 24));

After we calculate the days and such remaining, we set the state to equal the values we just calculated.

setState({ days: days, hours: hours, minutes, seconds });

Every time we set the state, React triggers a re-render of the content that changed.

Guess what that means?

Yep! Our countdown now updates every 1 second and displays the new time remaining 🎉

Conclusion

So that's how you use React Hooks to create a simple countdown component. You can find a working demo on Codepen.

If you liked this article, you can check me out on Twitter, or visit my website for more info.

Thanks for reading! 👋

Top comments (2)

Collapse
 
lloydinator profile image
Lloyd Miller • Edited

Thanks for this. It was great and really helpful. Might I suggest though that instead of setting the variable numbersToAddZeroTo and using multiple if statements, you instead use slice(). So for instance you can have: seconds = ('0' + seconds).slice(-2).

I think it's better this way plus if you look at the preview, you'll see that 'seconds' do not get prepended with zero. But again awesome tutorial! Very well laid out and easy to understand.

Collapse
 
gsto profile image
Glenn Stovall

Awesome tutorial! One note, you probably want to clear out the interval you created with useEffect as well, otherwise, it'll keep running after your component unmounts.

useEffect(() => {
  const tick = setInterval(() => setNewTime(), 1000); 
  return () => clearInterval(tick) 
}, []);
Enter fullscreen mode Exit fullscreen mode