How to convert Class components to Function components in React

21 Jul 2022
8 min read

In the world of React, there are two ways of writing a React component.

1. Class Component

A class component is an ES6 JavaScript class that extends from  React.Component class. React.Component objects have a state, meaning the object can hold information that can change over the lifetime of the object. They can also respond to lifecycle methods, like componentDidMount()componentDidUpdate(), and componentWillUnmount().

2. Function Component

Function components are simply JavaScript functions. Before the advent of hooks in React 16.8, they were mostly referred to as stateless components because they only accepted and returned data to be rendered to the DOM. With hooks, composing components in React has become more straightforward.

What’s the difference between class components and function components?

To put it simply, one is a class, and the other is a function.
Take these examples below. The first one is a class component while the second one is a functional component and they both do exactly the same thing.

// Example class component
class MyComponent extends React.Component {
  render() {
    return <p>Hello, {this.props.name}  
  }
}
//Example function component
function MyComponent(props) {
  return <p>Hello, {props.name}</p>
}

Both components take a prop (name) and render `Hello, {name}`.
The class component needs to extend the React Component class and must specify a render method. Whereas the function component is simply a function, and the render method is simply the return value of the function.

Why convert a class component to a function component

  • If you’ve got a slightly older codebase, and you’d like to refactor some of your components into functional components.
  • While migrating our Sangam application from React v16 to React v18, we discovered an issue where a component was failing to perform a specific action that it was performing prior to upgrading. We found that the Automatic Batching introduced in React v18 was not functioning properly within a class component. However, this didn’t happen when we converted the class component into functional. These kinds of compatibility issues are less likely to be raised in function components.

Beware! Not all class components can be converted to functions! There are some cases where you need to use a class component. But 99% of the time you’ll be fine with a function component.

When can’t you use a function component?

There are some use cases where a function component simply won’t work. Let’s take a look at a few of them

  • If you need a constructor

If you really need a constructor, you’re gonna have a bad time. A constructor runs once and only exactly once, before the first render of the component. Currently, I haven’t found a hook that will replace this functionality (know of one? Let me know in the comments)
Anyways, most of the time all that’s being done in the constructor of a class component is setting up state, and binding event listeners. Both these things are handled differently in function components and so we’re good.

  • If you need to extend a component

In Javascript, classes can extend other classes, thus inheriting the parent’s prototype. In fact, if you’re creating a class component, you have to extend the base component from React. This is more or less not possible with function components.

  • Higher order components

You can make a HOC (higher-order component) with a function, however, it can often be a bit easier to use a class.

  • Side-effects of combined state updates

this.setState is no longer available in a function component. Instead, we use the useState hook, which returns a state variable, and an updater function. If you have some peculiar pattern where you’re updating a couple of state variables at once, and need to run a specific side effect, you might find this difficult (not impossible) with a function component.
As an example, if you do this

class MyComponent extends React.Component {
  onSomeEventHandler(newName) {
    this.setState({
      counter: this.state.counter+1,
      name: newName
    }, () => {
      console.log('Counter AND name have been updated!')
    });
  }
}

You’re going to struggle to exactly replicate that functionality with a function component.

Quick steps to convert to a function component

1. Change the class to a function
Change this

class MyComponent extends React.Component {
  //...
}

to this

function MyComponent(props) {
  //...
}

2. Remove the render method
Remove the render method, but keep everything after & including the return. Make this the last statement in your function.
Change this

//...
  render() {
    return (<p>Hello, World</p>);
  }
//...

To this

function MyComponent(props) {
  //...
  return (<p>Hello, World</p>);
} // end of function

3. Convert all methods to functions
Class methods won’t work inside a function, so lets convert them all to functions.

class MyComponent extends React.Component {
  onClickHandler(e) {
    // ...
  }
}
function MyComponent {
  const onClickHandler = (e) => {
    //...
  }
}

4. Remove references to this
The this variable in your function isn’t going to be super useful any more. Remove the references to it throughout your render and functions.
Change this

class MyComponent(props) extends React.Component {
  //...
  mySpecialFunction() {
    console.log('you clicked the button!')
  }
  onClickHandler(e) {
    this.mySpecialFunction();
  }
  render() {
    return (
      <div>
        <p>Hello, {this.props.name}</p>
        <button onClick={this.onClickHandler}>Click me!</button>
      </div>
    );
  }
}

To this

function MyComponent(props) {
  //...
  const mySpecialFunction = () => {
    console.log('you clicked the button!')
  }
  const onClickHandler = (e) => {
    mySpecialFunction();
  }
  return (
    <div>
      <p>Hello, {props.name}</p>
      <button onClick={onClickHandler}>Click me!</button>
    </div>
  );
}

5. Remove constructor
Simply removing the constructor is a little tricky, so I’l break it down further.

  • useState

Instead of

constructor(props) {
  super(props);
  //Set initial state
  this.state = {
    counter: 0,
    name: ""
  }
}

Use the useState hook

function MyComponent(props) {
  const [counter,setCounter] = useState(0);
  const [name,setName] = useState("");
}


6. Remove event handler bindings

We don’t need to bind event handlers any more with function components. So if you were doing this;

constructor(props) {
  this.onClickHandler = this.onClickHandler.bind(this);
}

You can simply remove these lines.

7. Replace 
this.setState
this.setState obviously doesn’t exist any more in our function component. Instead we need to replace each of our setState calls with the relevant state variable setter.
Replace this;

class MyComponent extends React.Component {
  onClickHandler(e) {
    this.setState({count: this.state.count+1})
  }
}

With this;

function MyComponent {
  const [count, setCount] = useState(0)
  const onClickHandler = e => {
    setCount(count+1);
  }
}


8. 
useEffect for state update side effects
In a class component, this.setState could accept a callback that would run after the state was updated. Well, the useState updater function does no such thing. Instead we have to use the useEffect hook. It doesn’t work exactly the same though! useEffect will trigger whenever one of it’s dependencies are changed.
If you do this;

this.setState({counter: this.state.counter+1}, () => {
  console.log('Counter was updated!')
})

Do this instead

const [counter, setCounter] = useState(0)
useEffect(() => {
  console.log('counter changed!')
}, [counter])

Beware! The function inside useEffect will run each time the variable counter gets updated.

9. Replace lifecycle methods with hooks


  • componentDidMount

Instead of using the componentDidMount method, use the useEffect hook with an empty dependency array.

useEffect(()=>{
  console.log('component mounted!')
},[]) //notice the empty array here
  • componentWillUnmount

Instead of using the componentWillUnmount method to do cleanup before a component is removed from the React tree, return a function from the useEffect hook with an empty dependency array;

useEffect(() => {
  console.log('component mounted')
  // return a function to execute at unmount
  return () => {
    console.log('component will unmount')
  }
}, []) // notice the empty array
  • componentDidUpdate

If you pass nothing as the second argument to useEffect, it will trigger whenever a component is updated. So instead of using componentDidUpdate, use;

useEffect(() => {
  console.log('component updated!')
}) // notice, no second argument

Final Note

There are pros and cons in both styles but I would like to conclude that functional components are taking over modern React in the foreseeable future.
As we noticed in the examples, a functional component is written shorter and simpler, which makes it easier to develop, understand, and test. Class components can also be confusing with so many uses of this keyword. Using functional components can easily avoid this kind of a mess and keep everything clean.

It should also be noted that the React team is supporting more React hooks for functional components that replace or even improve upon class components. To follow up, the React team mentioned in earlier days that they will make performance optimizations in functional components by avoiding unnecessary checks and memory allocations. The React team is seeking to gradually adopt functional components with hooks in newer cases, which means that there is no need to switch over the complex existing projects that utilize class components and entirely rewrite with functional components.

React hooks

I hope this article helped you get more familiar with modern React. Feel free to comment with any questions or suggestions.

References: