Petros Kyriakoupersonal blog

I am a full stack web developer. Love tinkering all the time, especially anything javascript related.

Tips for converting a class component to a functional one in React

March 29, 2022

Lately, I have been doing a lot of refactoring work, and I have come to observe some patterns when converting class components to their functional equivalent in React.

Let's take a look at an example class component and my approach to converting it to a functional one.

Example

class MyComponent extends React.Component {
  constructor (props) {
    super (props)
    this.state = {
      count: props.initialCount,
      countB: 0,
      showComponent: false
    }
  }

  increment () {
    this.setState({
      count: this.state.count + 1
    })
  }

  render() {
    const {showComponent} = this.state

    return <div>
      <span>Hello World</span>
      {showComponent && <MyComponent2 />}
    </div>;
  }
}
export default MyComponent

While a bit contrived the example above gives us a good baseline for most of the class components that I have come across.

Step 1 - Convert class declaration into a function declaration and export it directly

export function MyComponent () {
  constructor (props) {
    super (props)
    this.state = {
      count: props.initialCount,
      countB: 0,
      showComponent: false
    }
  }

  increment () {
    this.setState({
      count: this.state.count + 1
    })
  }

  render() {
    const {showComponent} = this.state

    return <div>
      <span>Hello World</span>
      {showComponent && <MyComponent2 />}
    </div>;
  }
}

As can be seen above I converted the first line to reflect a function and also converted the component to a named export (personal preference).

Step 2 - Extract state into hooks

There are two main ways to go about it. Either we extract each state property into a React.useState hook, or we extract the entire state into a React.useState hook or we create a single React.useState hook. In this case I am going to with the latter

export function MyComponent ({initialCount}) {
  const [state, setState] = React.useState({
    count: initialCount,
    countB: 0,
    showComponent: false
  })

  increment () {
    this.setState({
      count: this.state.count + 1
    })
  }

  render() {
    const {showComponent} = this.state

    return <div>
      <span>Hello World</span>
      {showComponent && <MyComponent2 />}
    </div>;
  }
}

Two things happened above.

  1. First I extracted the state into a React.useState hook. This has the benefit of needing minimal changes to the components in terms of state as the acessibility of state happens using the state object as in class components
  2. I destructured the props variable directly into the function arguments

Step 3 - Declare class methods to be functions

export function MyComponent ({initialCount}) {
  const [state, setState] = React.useState({
    count: initialCount,
    countB: 0,
    showComponent: false
  })
  
  function increment () {
    this.setState({
      count: this.state.count + 1
    })
  }
  
  render() {
    const {showComponent} = this.state
  
    return <div>
      <span>Hello World</span>
      {showComponent && <MyComponent2 />}
    </div>;
  }
}

This should be an intiuitive one. Essentially we add the keyword function to every class method declaration

Step 4 - Remove the render method

export function MyComponent ({initialCount}) {
  const [state, setState] = React.useState({
    count: initialCount,
    countB: 0,
    showComponent: false
  })
  
  function increment () {
    this.setState({
      count: this.state.count + 1
    })
  }
  
 
  const {showComponent} = this.state

  return <div>
    <span>Hello World</span>
    {showComponent && <MyComponent2 />}
  </div>;
}

As we know (or not) functional components don't have a render method. So I removed it.

Step 5 - Update setState to include previous state

export function MyComponent ({initialCount}) {
  const [state, setState] = React.useState({
    count: initialCount,
    countB: 0,
    showComponent: false
  })
  
  function increment () {
    this.setState(prev => ({
      ...prev,
      count: prev.count + 1
    }))
  }
 
  const {showComponent} = this.state

  return <div>
    <span>Hello World</span>
    {showComponent && <MyComponent2 />}
  </div>;
}

A key different between this.setState and React.useState is that in class components this.setState updates the state partially, whereas the React.useState setState overwrites the state.

Hence, to avoid that, we can expose and include the previous state like above.

Step 6 - Remove all this references

export function MyComponent ({initialCount}) {
  const [state, setState] = React.useState({
    count: initialCount,
    countB: 0,
    showComponent: false
  })
  
  function increment () {
    setState(prev => ({
      ...prev,
      count: prev.count + 1
    }))
  }

  return <div>
    <span>Hello World</span>
    {state.showComponent && <MyComponent2 />}
  </div>;
}

And finally, we have a functional component equivalent to the class component we started with.

Conclusion

I have tried this 6-step in several occasions and it worked like a treat! Also of note is that the order of steps is not that important. I will let you be the judge though.