React Hooks: Migrate class component to functional and use hooks

elanandkumar

Anand Kumar

Posted on May 12, 2019

React Hooks: Migrate class component to functional and use hooks

We all are aware that react hooks is ready for prime time and we started using it already. But, what if we have any existing project and we want to move to react hooks? Definitely, we have components which is written as class component.

This post will guide you to change an existing class based component to react hook based component. But, this post is not intended to go into the details of hooks.

This is what react documentation says about hooks.

Hooks let us use state and other React features without writing a class.

Let's begin with an example application. It is a simple counter application. The code is given below:

const initialData = {
  count: 0,
  maxLimit: 10
};

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: this.props.initialCount
    };
  }
  onIncrement = () => {
    this.setState({ count: this.state.count + 1 });
  };
  onDecrement = () => {
    this.setState({ count: this.state.count - 1 });
  };
  render() {
    return (
      <div>
        <button
          onClick={this.onDecrement}
          disabled={this.state.count === this.props.initialCount}
        >
          -
        </button>
        <div>{this.state.count}</div>
        <button
          onClick={this.onIncrement}
          disabled={this.state.count === this.props.maxLimit}
        >
          +
        </button>
      </div>
    )
  }
}

class CounterApp extends React.Component {
  render() {
    const { initialData } = this.props;
    return (
      <React.Fragment>
        <h2>Counter Example</h2>
        <Counter
          initialCount={initialData.count}
          maxLimit={initialData.maxLimit}
        />
      </React.Fragment>
    );
  }
}

ReactDOM.render(
  <CounterApp initialData={initialData} />,
  document.getElementById('root')
);

As you can see, I have created a simple counter app. The class component that I have created are:

  • CounterApp: This is the main app component
  • Counter: The actual counter component

Please note that, this code is intended for this post and for explanation purpose only. Please ignore any improvement areas that you may see. ☺

So, to convert a class component to functional/hooks component, here are the steps:

Pick the smallest class component first i.e. bottom to top approach would be better.

  1. Move all of the code that a class component has into the render method and make it work. Eg. event handler functions or any other functions or code.
  2. Update the code to use object de-structuring for props & states.
  3. Once, #2 is complete, Add a line before the class syntax like shown below.
const Counter = () => {};
class Counter extends React.Component {
  /*...code goes here */
};
  1. Move everything from render method to the updated function component code block.
const Counter = () => {
  //...code inside render methods of class component goes here
};
class Counter extends React.Component {
  //...Rest of the code
  render() {
    // This will be blank now
  }
};
  1. Move the de-structured code of props inside the paratenthis of functional component. Remove the code from inside the code block.
const Counter = ({ maxLimit, initialCount }) => {
  /*...rest of the code */
};
  1. Update the component to use hooks like useState or any other hooks that might be needed. Eg.
const Counter = ({ maxLimit, initialCount }) => {
  //...Rest of the code
  const [count, setCount] = React.useState(initialCount);
  //...Rest of the code
};

If you have more that one state property, please change it to use hooks for all of them individually.

I am not explaining the syntax for how to write hooks here. Please read official documentation for the same.

  1. Finally, update the return statement code block to use newly created props (de-structured) or the states using hooks. Or any other changes done, should be incorporated accordingly. And remove the class component line of code altogether.

Alright, that might look too much and not very clear. So let's work on the example that we have.

Counter Component

  • Move the onIncrement and onDecrement code block inside the render method. Define it as const and update the functions this.onIncrement and this.onDecrement to onIncrement and onDecrement respectively.
  • Use object de-structuring for props and state inside render method. In our example, you can do something like below:
const { maxLimit, initialCount } = this.props;
const { count } = this.state;
  • Now, just before the class Counter extends React.Component { line, create a line similar to following:
const Counter = () => {};
  • Cut everything from render method and paste it in between the curly brackets of the const Counter = () => {}
  • Move the de-structured props into parenthesis. Line 2 is commented out and Line 1 has the props now {maxLimit, initialCount}.
const Counter = ({ maxLimit, initialCount }) => {
  // const { maxLimit, initialCount } = this.props;
  const { count } = this.state;
  // ....Rest of the code
}
  • Next, we need to change our this.state. We will be using useState hooks here. Update the code at Line 2 as shown below:
const Counter = ({ maxLimit, initialCount }) => {
  const [count, setCount] = React.useState(initialCount);
  // ....Rest of the code
}

The code that we changed is syntax of using hooks.

  • Now is the time to update our function to use setCount we defined using hooks. Change the code this.setState that we have inside onIncrement and onDecrement. Updated code will look something like below:
const Counter = ({ maxLimit, initialCount }) => {
  // ....Rest of the code
  const onIncrement = () => {
    setCount(count + 1);
  };
  const onIncrement = () => {
    setCount(count - 1);
  };
  // ....Rest of the code
}
  • Now, update the code of return statement accordingly to use the new count state (created using hooks) and de-structured props. AND, remove the class Counter extends React.Component {} code completely.

The final code for Counter component should look like:

const Counter = ({ maxLimit, initialCount }) => {
  const [count, setCount] = React.useState(initialCount);
  const onIncrement = () => {
    setCount(count + 1);
  };
  const onDecrement = () => {
    setCount(count - 1);
  };
  return (
    <div>
      <button
        onClick={onDecrement}
        disabled={count === initialCount}
      >
        -
      </button>
      <div>{count}</div>
      <button
        onClick={onIncrement}
        disabled={count === maxLimit}
      >
        +
      </button>
    </div>
  )
}

So, you must notice that at the end, there is no this being used and code also looks lot more cleaner.

CounterApp

The main CounterApp component doesn't have much in it and so we can convert it to functional component without hassle. The final code for CounterApp will look like below.

const CounterApp = ({ initialData: {count, maxLimit} }) => {
  return (
    <>
      <h2>Counter Example</h2>
      <Counter initialCount={count} maxLimit={maxLimit} />
    </>
  )
}

Wondering what <> and </> is? This is an approach to avoid extra layer of elements that we used to created earlier. So, use this or use React.Fragment as shown at the beginning of the article.

As, we have refactored our code now, the complete counter app code is given below:

const initialData = {
  count: 0,
  maxLimit: 10
};
// added style to make it look better, in case you want to see
const counterStyles = {
  h1: {
    maxWidth: "185px",
    boxShadow: "5px 5px 4px #888888"
  },
  container: {
    display: "flex",
    alignItems: "center"
  },
  button: {
    width: "30px",
    borderRadius: "50%",
    borderStyle: "none",
    fontSize: "22px",
    backgroundColor: "khaki",
    height: "30px",
    paddingBottom: "5px",
    cursor: "pointer"
  },
  countDiv: {
    minWidth: "80px",
    padding: "0 15px",
    textAlign: "center",
    height: "30px",
    lineHeight: 2,
    border: "1px solid",
    borderRadius: "10px",
    margin: "0 5px"
  }
};

const Counter = ({ maxLimit, initialCount }) => {
  // styles...
  const { container, button, countDiv } = counterStyles;
  const [count, setCount] = React.useState(initialCount);
  const onIncrement = () => {
    setCount(count + 1);
  };
  const onDecrement = () => {
    setCount(count - 1);
  };
  return (
    <div style={container}>
      <button
        style={button}
        onClick={onDecrement}
        disabled={count === initialCount}
      >
        -
      </button>
      <div style={countDiv}>{count}</div>
      <button
        style={button}
        onClick={onIncrement}
        disabled={count === maxLimit}
      >
        +
      </button>
    </div>
  );
};

const CounterApp = ({ initialData: {count, maxLimit} }) => {
  return (
    <>
      <h2 style={counterStyles.h1}>Counter App: Hooks</h2>
      <Counter initialCount={count} maxLimit={maxLimit} />
    </>
  );
};

ReactDOM.render(
  <CounterApp initialData={initialData} />,
  document.getElementById('root')
)

Summary

Through this post, we have learnt couple of tips on how to convert a class based component to functional and hook based component. I hope this will help you to migrate your existing code base.

I have also recorded a video to accompany this article. Here is the link.

Feel free to subscribe to the channel and explore other videos too.

In case of any questions/concerns, feel free to reach out.

Happy Learning!

💖 💪 🙅 🚩
elanandkumar
Anand Kumar

Posted on May 12, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related