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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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!