DEV Community

loading...

Convert Your Old Class Components to Functions. Just Do It Already!

Daniel Hintz
Junior full-stack developer with focus in JavaScript (ES6) and Ruby. Experience with React, Redux, Rails, among others.
・5 min read

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 {};
Enter fullscreen mode Exit fullscreen mode

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
  };
Enter fullscreen mode Exit fullscreen mode

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>
    );
  };
};
Enter fullscreen mode Exit fullscreen mode

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>
    );
  };
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Function-Based Clock Component Class-Based Clock Component

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:

  1. Change the import statement
    • From: import React, {Component} from 'react'
    • To: import React, {useState, useEffect} from 'react'
  2. Change the component declaration
    • From: export default class Clock extends Component {}
    • To: const Clock = () => { & move the export to the end of the file
  3. Change the state definition
    • From: state = {currentTime: new Date};
    • To: const [currentTime, setCurrentTime] = useState(new Date);
  4. Drop this.state. from anywhere that uses state data
  5. Change any this.setState() to the new function defined in useState
  6. Change any instance methods/variables to regular functions/variables
    • From: tick() {} / this.int =
    • To: function tick() {}; / int =
    • Alt: convert function to arrow function tick = () => {}
  7. Finally, change any lifecycle methods to useEffect()
    • From: componentDidMount() {}; / componentWillUnmount() {}
    • To: useEffect() which returns a cleanup function

Discussion (2)

Collapse
revolist profile image
Maks

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.

Collapse
dhintz89 profile image
Daniel Hintz Author

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?