This page looks best with JavaScript enabled

Guide to Converting Class-Based Components into Functional in React: Strategy, Gotchas, and Tips

 ·  ☕ 7 min read  ·  ✍️ Iskander Samatov

react-convert-class-components-to-functional


This guide will discuss the general approach I like to take when converting class-based components to functional format. We’ll also cover some common gotchas you may encounter along the way.

Wrong reasons to convert

Before we start, let’s go over a few reasons why you shouldn’t convert your class-based components to functional ones.

Change for the sake of change

The functional approach is the preferred way to build new React components, but that doesn’t mean you need to convert all your existing ones.

Unless you’re missing out on features that hooks give you, there’s no need to go through the effort of rewriting them.

There might be cases where you have to work with a functional component, say you have a library that exposes hooks for its API. Well, the beauty of React is that components are like lego pieces. So instead of rewriting your class-based component, you could create a functional wrapper that would deal with the hooks and pass down the result as props.

Rewriting code just to conform with the newest approach might not be the best use of your time.

Abstracting logic into hooks

Hooks are great for sharing logic among components, but they are not the only method of sharing code.

Before hooks, developers would use higher-order components ( HOCs ) to reuse stateful code. HOCs are still a valid way of sharing logic, and they may be a better fit if your codebase contains a lot of class-based components.

Making components work together

Sometimes, it might seem difficult to make functional and class-based components work together nicely. But it’s actually pretty simple if you use wrappers.

You can create a functional wrapper around the class-based component. The wrapper can deal with the hooks you need and pass down the result to a class-based component with props.

This way functional component can take care of the hooks, and the class-based component can retain all its existing logic.

The difference in component lifecycle methods

Before we begin covering our approach to converting, let’s go over the difference between the lifecycle methods of functional and class-based components.

ComponentDidMount and useEffect

When converting a class-based component containing componentDidMount lifecycle method, you need to use the useEffect hook instead.

The useEffect hook can also be used in place of most of the other lifecycle methods like componentDidUpdate, componentWillUnmount, etc.

One key difference is that the useEffect hook will run on every component render by default. You can avoid this behavior by providing an empty array as a second argument to the useEffect hook, which tells React to run useEffect only when the component mounts for the first time, the same as componentDidMount.

ComponentDidUpdate and useEffect

Just like componentDidMount, the useEffect hook can be used in place of componentDidUpdate. In this case, you provide an array of dependencies as the second argument to useEffect. React will re-run the effect whenever any value in the dependency array changes.

The only difference is that useEffect doesn’t have access to the prevProps and prevState. But you can use the useRef hook to keep track of previous props as a workaround.

General approach

Now let’s cover how you would actually go about converting a class-based component to functional. This is the approach I found to be the safest and easily reproducible.
As an example, we’ll use this somewhat trivial class-based component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from "react";
import { connect } from "react-redux";
import { fetchAnnouncements } from "./actions";
import { someAPICall } from "./api";

class ClassBasedSample extends React.Component {
 componentDidMount() {
    someAPICall().then(() => {
        fetchAnnouncements();
    });
 }

 render() {
    return (
        <div>
            <h1>Announcement board</h1>
            <div>
                {this.props.announcements.map((item) => (
                    <p>{item.title}</p>
                ))}
            </div>
        </div>
    );
 }
}

const mapStateToProps = (state) => ({
 announcements: state.dashboard.announcements
});

const mapDispatchToProps = {
 fetchAnnouncements
};

export default connect(mapStateToProps, mapDispatchToProps)(ClassBasedSample);

When converting, it’s best to do everything in stages and focus on fully converting one component at a time.

Rather than making changes to the existing component, the first step is to create a copy. This way, you avoid introducing bugs, which is especially important if you’re component is used in the production environment.

Next, create the functional component that will take the place of the class-based one and render your class-based component inside of it.

1
2
3
4
5
import ClassBasedSample from "./ClassBasedSampleCopy";

export const FunctionalWrapper = () => {
    return <ClassBasedSample />;
};

Now we can start moving the logic of the old component to the new one.

You can start by moving the API calls and data fetching into the functional component. You can pass down the data from the API responses as props down to the class-based component.

1
2
3
4
5
6
7
export const FunctionalWrapper = () => {
 useEffect(() => {
    someAPICall();
 }, [dispatch]);

 return <ClassBasedSample />;
};

If the class-based component connects to Redux, you can move that logic as well. This is where you can simplify your Redux client code by removing connect and using hooks like useSelector and useDispatch inside your functional component. Same as before, you can pass data to the class-based component via the props.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
export const FunctionalWrapper = () => {
const dispatch = useDispatch();
const announcements = useSelector((state) => state.dashboard.announcements);

useEffect(() => {
    someAPICall().then(() => {
        dispatch(fetchAnnouncements());
    });
}, [dispatch]);

return <ClassBasedSample announcements={announcements} />;
};

Then pull the rest of the important class methods up into the functional component.
Continue stripping the class-based component down to just render() until the only thing left is JSX.
At this point, it’s pretty safe to move JSX into the functional component as well and delete the class-based component altogether.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
export const FunctionalWrapper = () => {
const dispatch = useDispatch();
const announcements = useSelector((state) => state.dashboard.announcements);

useEffect(() => {
    someAPICall().then(() => {
        dispatch(fetchAnnouncements());
    });
}, [dispatch]);

return (
    <div>
        <h1>Announcement board</h1>
        <div>
            {announcements.map((item) => (
                <p>{item.title}</p>
            ))}
        </div>
    </div>
);
};

And that’s it! You have successfully converted a class-based component into a functional one using a safe and methodical approach.

Tips and gotchas

Good time to refactor

Converting a class-based component to a functional one is also an opportunity to clean up and refactor your code.
Class-based components often end up with large render methods, so you can take this opportunity to split their JSX tree into smaller functional ones for better readability.

On top of that, you can further separate some of the data fetching and processing logic into hooks.
I suggest waiting with refactoring until you have finished converting the component to functional format and verified that it works as expected.

Inline props for better readability

Functional components allow destructing the component’s props inline right in the function declaration, something you can’t do with the class-based components.

1
2
3
4
5
6
7
export const FunctionalWrapper = ({ prop1, prop2 }) => {
 useEffect(() => {
    someAPICall();
 }, [dispatch]);

 return <ClassBasedSample />;
};

Destructuring props this way helps avoid a need to use the props keyword everywhere in your component and makes your code cleaner.

useState vs setState

Another thing to be aware of is the difference between useState and setState. Both perform the same task, but there is a difference in how those methods work.

useState doesn’t automatically merge a new state with the previous one, the same way setState does. So if you’re using useState to keep track of an object, you need to spread the previous state into the new one.

useState doesn’t provide a callback

Another thing to remember is that useState doesn’t provide an optional callback feature to run right after the state update. So if you need to perform some side effects after a state update, you can use the useEffect hook.

Conclusion

And that’s it for this post. Converting class-based components into functional ones is a pretty straightforward process. But there are a few things to keep in mind, which I hope this guide helped you with.

If you’d like to get more web development, React and TypeScript tips consider following me on Twitter, where I share things as I learn them.
Happy coding!

Share on

Software Development Tutorials
WRITTEN BY
Iskander Samatov
The best up-to-date tutorials on React, JavaScript and web development.