Refactoring React Class Components with Hooks

Mary Farner
Level Up Coding
Published in
6 min readJan 26, 2020

--

If you are like me, you’ve been told that functional components are more efficient, cleaner, or “just better” and that you should try out using hooks. But you’ve been avoiding it. Because you’re pretty comfy using setState() and lifecycle methods and it’s working…

But today is the day, folks. We’re going to do it. Because I finally tried it out, and it was shockingly easy to refactor, and the result is refreshingly clean and easy to follow code. I’m about to lay everything out in a really simple way, so there’s nothing to lose.

So double-knot your shoe laces, quit whining, and let’s go.

First, Just a Touch of Background…

Class Components vs. Functional Components

React Class Components are what programmers like to call “syntactic sugar” that came about with ES6 that allow us to automatically use convenient stuff like state and lifecycle methods such as componentDidMount() and componentDidUpdate(). Class components extend the React Component, and are declared and used as such:

Of course, React components can also be declared as simple functions (like everything else in JavaScript). The same component as above would be declared as such:

Functional components lead to much cleaner, leaner code after transpiling, and are generally easier to follow, test, and work with in general because they’re just plain old JavaScript function. But, historically, using a functional component meant that we couldn’t use things like state or lifecycle methods that are so convenient with React Components. So we would use a functional component for simply presentational components that only needed props and usually just render something. With the introduction of hooks, though, things changed a bit, and we can now use functional components for any component.

But What About State…

How is this possible if we need to use state? Introducing the useState() hook! Use state lets you define a state variable, a setter method for that variable, and initialize the value all at once. Check out the useState documentation for a more in-depth explanation, but usage looks something like this:

It replaces initializing state and using setState() in a class Component:

And Lifecycle Methods?

And what about things that need to happen throughout a component’s lifecycle, you ask? There are various other hooks we can use throughout the lifecycle, some that help make code even faster and more efficient, but the most common one is useEffect(). useEffect() runs its callback function anytime the component updates by default, but you can also tie it to changes in specific dependencies by passing a second parameter as an array of dependent variables.

So, useEffect can be used to replace componentDidMount and componentDidUpdate, and further can be tailored such that it triggers on only specific occasions. You can check out the documentation, but here are a few examples:

Ok, Let’s Go For It: An Example

With that bit of background, I find that the easiest way to dive into something is to just start doing.

See below for a very basic example component. I set it up to read a value from props, use state for a controlled form, and make an initial API fetch using componentDidMount. For the purposes of this example, let’s ignore what happens when we make that initial call or submit the form, and focus on the rest of the syntax.

Here’s the class component we’re going to work with:

Where to begin?

It seems like a lot of work, but if you just break it down, it becomes pretty methodical and foolproof. I would go about converting this to a functional component in the following steps:

  1. Change our React import statement
  2. Replace the declaration with function syntax
  3. Replace the initial state declaration with implementation(s) of useState
  4. Replace setState usage with the setters created in Step 2.
  5. Replace componentDidMount with useEffect.
  6. Get rid of the “render()” function
  7. Place ‘const’ in front of all function declarations
  8. Get rid of this.state. in front of all variables
  9. Get rid of ‘this’ everywhere else (in front of function calls)

So, let’s do it. Below, I will do each step, with the old Class version commented out directly above the functional version for clarity.

Change the import from React.

We no longer need Component. Instead, we need to import useState and useEffect:

2. Change the declaration syntax.

This is just a simple syntax adjustment. You know this…

3. Replace the initial state declaration with implementation(s) of useState.

Here, we create the variable and it’s setter (conventionally, “set” + “Variable name”) in an array, and then provide an initial value for the variable as an argument to useState:

4. Replace setState usage with the setters created in Step 2.

Now, just go through and any time this.setState appears, just use the appropriate getter! Two hints here. One, use CTRL+F to find setState! Two, for the controlled form, we can get rid of the handleChange function completely and just use the setters inline on the form:

5. Replace componentDidMount with useEffect.

Here, we use the exact same function body (barring any use of this. or this.state) for the body of the callback passed to useEffect. We pass an empty array as the second parameter to useEffect to signal that we want this function to run only on the initial mount.

Remember, we could pass nothing here to indicate it should run anytime the component updates at all, or an array of dependency variables whose change we want to trigger the function

6. Get rid of the “render()” function

This is as simple as commenting out a line…

7. Place ‘const’ in front of all function declarations

I don’t think you need a visual example here!

8. Get rid of this.state. in front of all variables

And

9. Get rid of ‘this’ everywhere else (in front of function calls)

Again, use your friend CTRL+F here!

And Viola! You have a functional component that utilizes state and lifecycle methods!

So What?

So why did we go through all of this? Well first of all, it makes the code a lot cleaner, shorter, and simpler. The functional component is 54 lines of code, the class component is 67 lines. More importantly, if you run each one through a JS compiler, the class component is 121 lines and the functional component is 85 lines long! But lines of code aside, it’s just easier to follow the functional component. The variable setters make updates to state read as (even more-so than before) plain english. We’re also in full control of when and why our useEffect callback function runs, and we can optimize the efficiency of the component.

In this example, we’re dealing with a very simple component, so we don’t even begin to brush the surface of how dynamic and fancy our hooks can be. But this covers nearly everything you need to know in order to get started converting your class components into functional components. As you go, you can dive into other hooks like useCallback and useMemo to make your components even more efficient, but if you just start by tackling one step at a time (like we did above), you’ll be cookin’ in no time.

Enjoy!

--

--