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)
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 useslice()
. 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.
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.