It's now generally acknowledged that using hooks in React is better than using classes. There are a ton of blogs and discussions on the subject and over time they have inched more and more uniformly toward hooks. Yet my React training still used classes, and I know that many developers still use classes today as well. My guess is a lot of them do so for the same reason I've been sticking to classes: I'm already familiar with class-based stateful components, and I'd have to re-learn how to implement state and lifecycles if I make the switch. Maybe that's not the greatest reason, but with so much out there that needs to be learned, it is hard to prioritize learning a new way of doing something when the "old way" is already working perfectly for me today.
But here's the thing - in React interviews, they expect you to use hooks. If you use a class you get the toughest interview question of them all..."Why did you do it that way?" 😨...😰...😢
Luckily, after a ahem learning experience finally convinced me it's time to learn about hooks, I found out they're really not bad at all, at least in the simplest implementations.
There are two "main" hooks and that was all I am initially concerned with: useState
and useEffect
. So below, I'm going to create an unstyled digital clock component, using both classes and hooks, to show off how to use both of these. At the end I'll do a side-by-side comparison.
Class
Before we can even get started, we need to set up the component. first, import React, and its Component class, and then create our Clock component that inherits from it.
import React, {Component} from 'react';
export default class Clock extends Component {};
Then, let's start by setting up our state. We need to create a Date Object representing the current date/time and set it in the component's state with a key of currentTime
.
state = {
currentTime: new Date
};
And then we can call our render function to display that value in the DOM. To convert it to a time string, we'll use toLocaleTimeString()
.
import React, {Component} from 'react';
export default class Clock extends Component {
state = {
currentTime: new Date
};
render() {
return(
<h2>{this.state.currentTime.toLocaleTimeString()}</h2>
);
};
};
And that will display the time on the page. But to make it a clock, we need it to "tick" each second as time goes by. We start by defining a tick()
function that sets the state to the new moment in time. Then we want to call that tick function every second by setting up a one second interval. For the interval, we need to wait until the component is mounted, then start the interval timer. To do something "once the component is mounted" we use the componentDidMount
lifecycle method. Finally, if and when the Clock component is unmounted, we'd want the interval to stop so that the computer isn't constantly counting for no reason. To do something "once the component is unmounted" we use componentWillUnmount
which runs just before the component is destroyed.
import React, {Component} from 'react';
export default class Clock extends Component {
state = {
currentTime: new Date
};
tick() {
this.setState({currentTime: new Date});
};
componentDidMount() {
this.int = setInterval(() => this.tick(), 1000);
};
componentWillUnmount() {
clearInterval(this.int);
};
render() {
return(
<h2>{this.state.currentTime.toLocaleTimeString()}</h2>
);
};
};
And now we have ourselves a ticking clock!
Hooks
Now, let's see how to do the exact same thing using hooks. Again, we need to start by setting up the component. Notice we need to move the export statement to the bottom now.
import React, {useState, useEffect} from 'react';
const Clock = () => {};
export default Clock;
Then, once again, we'll set up our state with the same key & value. Here, we're defining two separate variables at the same time. currentTime
is our key, and setCurrentTime
equates to calling this.setState()
in a class. Finally, calling useState
actually calls setCurrentTime
, so you need to pass an argument to set up the initial state, in this case a Date object.
const [currentTime, setCurrentTime] = useState(new Date);
What we render will remain unchanged, but since we're using a functional, not class, component we just need to return the JSX, we don't need to use the render()
function.
import React, {useState, useEffect} from 'react';
const Clock = () => {
const [currentTime, setCurrentTime] = useState(new Date);
return(
<h2>{this.state.currentTime.toLocaleTimeString()}</h2>
);
};
export default Clock;
And now it's time to get that clock a-ticking. We'll start by defining that tick()
function again which sets the state to the new moment. We do this by defining a normal function (there's no class for instance methods) which uses our new setCurrentTime
function variable to change state. Where things get interesting is, since there's no lifecycle methods without the class, we need to use useEffect()
. This function actually includes both the componentDidMount
and componentWillUnmount
methods all in one. We still need to define our interval and set the callback to tick()
, but now we will have our useEffect
function return another function. This returned function stands in for componentWillUnmount
and should be used to clean up any services that were started once the component is destroyed.
import React, {useState, useEffect} from 'react';
const Clock = () => {
const [currentTime, setCurrentTime] = useState(new Date);
function tick() {
setCurrentTime(new Date);
};
useEffect(() => {
let int = setInterval(() => tick(), 1000);
return cleanup => {
clearInterval(int);
};
});
return(
<h2>{currentTime.toLocaleTimeString()}</h2>
);
};
export default Clock;
Lastly, we can take this one step further by converting our functions to arrow functions. Take a look at the side-by-side below to see the refactored component.
Comparison
Hooks | Class |
---|---|
The functional way looks a lot more concise doesn't it? So what do you think...are you convinced to start using hooks yet, or do you need to learn the hard way like I did?
Since I like to have defined rules so that I can just follow down the list, here is your Converting Class To Function Checklist:
- Change the import statement
- From:
import React, {Component} from 'react'
- To:
import React, {useState, useEffect} from 'react'
- From:
- Change the component declaration
- From:
export default class Clock extends Component {}
- To:
const Clock = () => {
& move the export to the end of the file
- From:
- Change the state definition
- From:
state = {currentTime: new Date};
- To:
const [currentTime, setCurrentTime] = useState(new Date);
- From:
- Drop
this.state.
from anywhere that uses state data - Change any
this.setState()
to the new function defined inuseState
- Change any instance methods/variables to regular functions/variables
- From:
tick() {}
/this.int =
- To:
function tick() {};
/int =
- Alt: convert function to arrow function
tick = () => {}
- From:
- Finally, change any lifecycle methods to
useEffect()
- From:
componentDidMount() {};
/componentWillUnmount() {}
- To:
useEffect()
which returns a cleanup function
- From:
Top comments (2)
That’s nice, very simple and easy to read. There are some benefits in both approach class and func, but I think it’s other topic. Well done.
Thanks the comment! I haven't gone too deep into using hooks yet, so I'm sure I'll find some use case where a class would be beneficial (and I'll update here when I do), but the community seems to be pushing pretty hard for hooks nowadays.
Out of curiosity, do you happen to have an example of where a class may be a better implementation?