Tutorial

Five Ways to Convert React Class Components to Functional Components with React Hooks

JavaScriptReact

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

The latest alpha release of React introduced a new concept called Hooks. Hooks were introduced to React to solve common problems; however, they primarily serve as an alternative for classes. With Hooks, you can create functional components that uses state and lifecycle methods.

Hooks are currently available in React v16.7.0-alpha, though there are no plans to remove classes. Hooks just provide another way to write React.

Given that Hooks are still new, many developers are looking to apply the concept in their existing React applications or new applications. In this post, we’ll look at five ways to convert React class components to functional components using React Hooks.

Looking at a Class Without State or Lifecycle Methods

To begin, we will look at a React class that has neither state nor lifecycle components. This class alerts a name when a user clicks a button:

import React, { Component } from 'react';

class App extends Component {

  alertName = () => {
    alert('John Doe');
  };

  render() {
    return (
      <div>
        <h3> This is a Class Component </h3>
        <button onClick={this.alertName}> Alert </button>
      </div>
    );
  }
}

export default App;

Here we have a typical React class, which lacks state or a lifecycle method. It alerts a name when a button is clicked. The functional equivalent of this class will look like this:

import React from 'react';

function App() {
  const alertName = () => {
    alert(' John Doe ');
  };

  return (
    <div>
      <h3> This is a Functional Component </h3>
      <button onClick={alertName}> Alert </button>
    </div>
  );
};

export default App;

Like the first example, this functional class behaves in a typical way. We haven’t used Hooks or anything new yet. In these examples, we have no need for state or lifecycle.

We can change that now and look at situations where we have a class-based component with state and learn how to convert it to a functional component using Hooks.

Adding Hooks to Classes with State

Let’s consider a situation where we have a global name variable that we can update within the app from a text input field. In React, we handle cases like this by defining the name variable in a state object and calling setState() when we have a new value to update the name variable with:

import React, { Component } from 'react';

class App extends Component {
state = {
      name: ''
  }

  alertName = () => {
    alert(this.state.name);
  };

  handleNameInput = e => {
    this.setState({ name: e.target.value });
  };

  render() {
    return (
      <div>
        <h3> This is a Class Component </h3>
        <input
          type="text"
          onChange={this.handleNameInput}
          value={this.state.name}
          placeholder="Your name"
        />
        <button onClick={this.alertName}> Alert </button>
      </div>
    );
  }
}

export default App;

When a user types a name in the input field and clicks the Alert button, it alerts the name we’ve defined in state.

We can convert this entire class into a functional React component using Hooks:

import React, { useState } from 'react';

function App() {

  const [name, setName] = useState('John Doe');

  const alertName = () => {
    alert(name);
  };

  const handleNameInput = e => {
    setName(e.target.value);
  };

  return (
    <div>
      <h3> This is a Functional Component </h3>
      <input
        type="text"
        onChange={handleNameInput}
        value={name}
        placeholder="Your name"
      />
      <button onClick={alertName}> Alert </button>
    </div>
  );
};

export default App;

Here, we introduced the useState Hook. It allows us to make use of state in React functional components. With the useState() Hook, we can use state in this functional component. It uses a similar syntax with destructuring assignment for arrays. Consider this line :

const [name, setName] = useState("John Doe")

Here, name is the equivalent of this.state in a normal class components while setName is the equivalent of this.setState .

The initial value of the state in the useState() Hook comes from an argument. In other words, the useState() argument is the initial value of the state. In our case, we set it to "John Doe": this means that the initial state of the name in state is John Doe.

This code is an example of how you can convert a class-based React component with state to a functional component using Hooks. We can now look at a few more examples, including classes with multiple state properties.

Adding Hooks to Classes with Multiple State Properties

We have looked at how you might convert one state property with useState, but the same approach won’t quite work when you have multiple state properties. If, for example, we had two or more input fields for userName, firstName, and lastName, then we would have a class-based component with three state properties:

import React, { Component } from 'react';

class App extends Component {

    state = {
      userName: '',
      firstName: '',
      lastName: ''
    };

  logName = () => {
    // do whatever with the names ... let's just log them here
    console.log(this.state.userName);
    console.log(this.state.firstName);
    console.log(this.state.lastName);
  };

  handleUserNameInput = e => {
    this.setState({ userName: e.target.value });
  };
  handleFirstNameInput = e => {
    this.setState({ firstName: e.target.value });
  };
  handleLastNameInput = e => {
    this.setState({ lastName: e.target.value });
  };

  render() {
    return (
      <div>
        <h3> This is a Class Component </h3>
        <input
          type="text"
          onChange={this.handleUserNameInput}
          value={this.state.userName}
          placeholder="Your username"
        />
        <input
          type="text"
          onChange={this.handleFirstNameInput}
          value={this.state.firstName}
          placeholder="Your firstname"
        />
        <input
          type="text"
          onChange={this.handleLastNameInput}
          value={this.state.lastName}
          placeholder="Your lastname"
        />
        <button className="btn btn-large right" onClick={this.logName}>
          {' '}
          Log Names{' '}
        </button>
      </div>
    );
  }
}

export default App;

To convert this class to a functional component with Hooks, we’ll have to take a somewhat unconventional route. Using the useState() Hook, the above example can be written as:

import React, { useState } from 'react';

function App() {

  const [userName, setUsername] = useState('');
  const [firstName, setFirstname] = useState('');
  const [lastName, setLastname] = useState('');

  const logName = () => {
    // do whatever with the names... let's just log them here
    console.log(userName);
    console.log(firstName);
    console.log(lastName);
  };

  const handleUserNameInput = e => {
    setUsername(e.target.value);
  };
  const handleFirstNameInput = e => {
    setFirstname(e.target.value);
  };
  const handleLastNameInput = e => {
    setLastname(e.target.value);
  };

  return (
    <div>
      <h3> This is a functional Component </h3>

      <input
        type="text"
        onChange={handleUserNameInput}
        value={userName}
        placeholder="username..."
      />
      <input
        type="text"
        onChange={handleFirstNameInput}
        value={firstName}
        placeholder="firstname..."
      />
      <input
        type="text"
        onChange={handleLastNameInput}
        value={lastName}
        placeholder="lastname..."
      />
      <button className="btn btn-large right" onClick={logName}>
        {' '}
        Log Names{' '}
      </button>
    </div>
  );
};

export default App;

This demonstrates how we can convert a class-based component with multiple state properties to a functional component using the useState() Hook.

Here’s a Codesandbox for this example.

Adding Hooks to a Class with State and componentDidMount

Let’s consider a class with state and componentDidMount. To demonstrate, we will look at a scenario where we set an initial state for the three input fields and have them all update to a different set of values after five seconds.

To achieve this, we will declare an initial state value for the input fields and implement a componentDidMount() lifecycle method that will run after the initial render to update the state values:

import React, { Component, useEffect } from 'react';

class App extends Component {
    state = {
      // initial state
      userName: 'JD',
      firstName: 'John',
      lastName: 'Doe'
  }

  componentDidMount() {
    setInterval(() => {
      this.setState({
        // update state
        userName: 'MJ',
        firstName: 'Mary',
        lastName: 'jane'
      });
    }, 5000);
  }

  logName = () => {
    // do whatever with the names ... let's just log them here
    console.log(this.state.userName);
    console.log(this.state.firstName);
    console.log(this.state.lastName);
  };

  handleUserNameInput = e => {
    this.setState({ userName: e.target.value });
  };
  handleFirstNameInput = e => {
    this.setState({ firstName: e.target.value });
  };
  handleLastNameInput = e => {
    this.setState({ lastName: e.target.value });
  };

  render() {
    return (
      <div>
        <h3> The text fields will update in 5 seconds </h3>
        <input
          type="text"
          onChange={this.handleUserNameInput}
          value={this.state.userName}
          placeholder="Your username"
        />
        <input
          type="text"
          onChange={this.handleFirstNameInput}
          value={this.state.firstName}
          placeholder="Your firstname"
        />
        <input
          type="text"
          onChange={this.handleLastNameInput}
          value={this.state.lastName}
          placeholder="Your lastname"
        />
        <button className="btn btn-large right" onClick={this.logName}>
          {' '}
          Log Names{' '}
        </button>
      </div>
    );
  }
}

export default App;

When the app runs, the input fields will have the initial values we’ve defined in the state object. These values will then update to the values we’ve defined inside the componentDidMount() method after five seconds.

Next, we will convert this class to a functional component using the React useState and useEffect Hooks:

import React, { useState, useEffect } from 'react';

function App() {

  const [userName, setUsername] = useState('JD');
  const [firstName, setFirstname] = useState('John');
  const [lastName, setLastname] = useState('Doe');

  useEffect(() => {
    setInterval(() => {
      setUsername('MJ');
      setFirstname('Mary');
      setLastname('Jane');
    }, 5000);
  });

  const logName = () => {
    // do whatever with the names ...
    console.log(this.state.userName);
    console.log(this.state.firstName);
    console.log(this.state.lastName);
  };

  const handleUserNameInput = e => {
    setUsername({ userName: e.target.value });
  };
  const handleFirstNameInput = e => {
    setFirstname({ firstName: e.target.value });
  };
  const handleLastNameInput = e => {
    setLastname({ lastName: e.target.value });
  };

  return (
    <div>
      <h3> The text fields will update in 5 seconds </h3>
      <input
        type="text"
        onChange={handleUserNameInput}
        value={userName}
        placeholder="Your username"
      />
      <input
        type="text"
        onChange={handleFirstNameInput}
        value={firstName}
        placeholder="Your firstname"
      />
      <input
        type="text"
        onChange={handleLastNameInput}
        value={lastName}
        placeholder="Your lastname"
      />
      <button className="btn btn-large right" onClick={logName}>
        {' '}
        Log Names{' '}
      </button>
    </div>
  );
};

export default App;

In terms of functionality, this component does exactly the same thing as the previous example. The only difference is that instead of using the conventional state object and componentDidMount() lifecycle method as we did in the class component, we used the useState and useEffect Hooks. Here’s a Codesanbox for this example.

Adding Hooks to a Class with State, componentDidMount, and componentDidUpdate

Next, let’s look at a React class with state and two lifecycle methods: componentDidMount and componentDidUpdate. So far, you may have noticed that we’ve mostly been working with the useState Hook. In this example, we will focus on the useEffect Hook.

To best demonstrate how this works, let’s update our code to dynamically update the <h3> header on the page. Currently the header says This is a Class Component. Now we’ll define a componentDidMount() method to update the header to say Welcome to React Hooks after three seconds:

import React, { Component } from 'react';

class App extends Component {
state = {
      header: 'Welcome to React Hooks'

  }

  componentDidMount() {
    const header = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      header.innerHTML = this.state.header;
    }, 3000);
  }

  logName = () => {
    // do whatever with the names ...
  };

  // { ... }

  render() {
    return (
      <div>
        <h3 id="header"> This is a Class Component </h3>
        <input
          type="text"
          onChange={this.handleUserNameInput}
          value={this.state.userName}
          placeholder="Your username"
        />
        <input
          type="text"
          onChange={this.handleFirstNameInput}
          value={this.state.firstName}
          placeholder="Your firstname"
        />
        <input
          type="text"
          onChange={this.handleLastNameInput}
          value={this.state.lastName}
          placeholder="Your lastname"
        />
        <button className="btn btn-large right" onClick={this.logName}>
          {' '}
          Log Names{' '}
        </button>
      </div>
    );
  }
}

export default App;

When the app runs, it will start with the initial header This is a Class Component and change it to Welcome to React Hooks after three seconds. This is the classic componentDidMount() behavior since it runs after the render function is executed successfully.

What if we want to dynamically update the header from another input field, so that the header gets updated with the new text while we type? To do that, we’ll need to implement the componentDidUpdate() lifecycle method like this:

import React, { Component } from 'react';

class App extends Component {
  state = {
      header: 'Welcome to React Hooks'
  }

  componentDidMount() {
    const header = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      header.innerHTML = this.state.header;
    }, 3000);
  }

  componentDidUpdate() {
    const node = document.querySelectorAll('#header')[0];
    node.innerHTML = this.state.header;
  }

  logName = () => {
    // do whatever with the names ... let's just log them here
    console.log(this.state.username);
  };

  // { ... }

  handleHeaderInput = e => {
    this.setState({ header: e.target.value });
  };

  render() {
    return (
      <div>
        <h3 id="header"> This is a Class Component </h3>
        <input
          type="text"
          onChange={this.handleUserNameInput}
          value={this.state.userName}
          placeholder="Your username"
        />
        <input
          type="text"
          onChange={this.handleFirstNameInput}
          value={this.state.firstName}
          placeholder="Your firstname"
        />
        <input
          type="text"
          onChange={this.handleLastNameInput}
          value={this.state.lastName}
          placeholder="Your lastname"
        />
        <button className="btn btn-large right" onClick={this.logName}>
          {' '}
          Log Names{' '}
        </button>
        <input
          type="text"
          onChange={this.handleHeaderInput}
          value={this.state.header}
        />{' '}
      </div>
    );
  }
}

export default App;

Here, we have state, componentDidMount() and componentDidUpdate() . So far, when you run the app, the header will update to Welcome to React Hooks after three seconds, thanks to our componentDidMount() function. When you start typing in the header text input field, the <h3> text will update with the input text as defined in the componentDidUpdate() method.

Next, we will convert this class to a functional component with the useEffect() Hook:

import React, { useState, useEffect } from 'react';

function App() {

  const [userName, setUsername] = useState('');
  const [firstName, setFirstname] = useState('');
  const [lastName, setLastname] = useState('');
  const [header, setHeader] = useState('Welcome to React Hooks');

  const logName = () => {
    // do whatever with the names...
    console.log(userName);
  };

  useEffect(() => {
    const newheader = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      newheader.innerHTML = header;
    }, 3000);
  });

  const handleUserNameInput = e => {
    setUsername(e.target.value);
  };
  const handleFirstNameInput = e => {
    setFirstname(e.target.value);
  };
  const handleLastNameInput = e => {
    setLastname(e.target.value);
  };
  const handleHeaderInput = e => {
    setHeader(e.target.value);
  };

  return (
    <div>
      <h3 id="header"> This is a functional Component </h3>

      <input
        type="text"
        onChange={handleUserNameInput}
        value={userName}
        placeholder="username..."
      />
      <input
        type="text"
        onChange={handleFirstNameInput}
        value={firstName}
        placeholder="firstname..."
      />
      <input
        type="text"
        onChange={handleLastNameInput}
        value={lastName}
        placeholder="lastname..."
      />
      <button className="btn btn-large right" onClick={logName}>
        {' '}
        Log Names{' '}
      </button>
      <input type="text" onChange={handleHeaderInput} value={header} />
    </div>
  );
};

export default App;

We achieved the same functionality with this component as we did with the previous using the useEffect() Hook. We optimized the code as well, since we didn’t have to write separate code for the componentDidMount() and componentDidUpdate() functions. With the useEffect() Hook, we get the functionality of both. This is because useEffect() runs both after the initial render and after every subsequent update by default.

Check out this example on this CodeSandbox.

Converting PureComponent to React memo

React PureComponent works in a similar manner to Component. The major difference between them is that React.Component doesn’t implement the shouldComponentUpdate() lifecycle method while React.PureComponent does. If you have an application where the render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.

Related Reading: React 16.6: React.memo() for Functional Components Rendering Control

React.memo() works in a similar way. When your function component renders the same result given the same props, you can wrap it in a call to React.memo() to enhance performance. Using PureComponent and React.memo() gives React applications a considerable increase in performance as it reduces the number of render operations in the app.

Here, we’ll demonstrate how to convert a PureComponent class to a React memo component. To understand what they both do, we will first look at code where a component renders every two seconds, whether or not there’s a change in value or state:

import React, { Component } from 'react';

function Unstable(props) {
  // monitor how many times this component is rendered
  console.log(' Rendered this component ');
  return (
    <div>
      <p> {props.value}</p>
    </div>
  );
};

class App extends Component {
  state = {
    value: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }

  render() {
    return (
      <div>
        <Unstable value={this.state.value} />
      </div>
    );
  }
}
export default App;

When you run the app and check the logs, you’ll notice that it renders the component every two seconds, without any change in state or props. This is a situation that we can improve with both PureComponent and React.memo().

Most of the time, we only want to re-render a component when there’s been a change in state or props. Using the example above, we can improve it with PureComponent so that the component only re-renders when there’s a change in state or props. We do this by importing PureComponent and extending it like this:

import React, { PureComponent } from 'react';

function Unstable(props) {
  console.log(' Rendered Unstable component ');
  return (
    <div>
      <p> {props.value}</p>
    </div>
  );
};

class App extends PureComponent {
  state = {
    value: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }

  render() {
    return (
      <div>
        <Unstable value={this.state.value} />
      </div>
    );
  }
}
export default App;

Now if you run the app again, you only get the initial render. Nothing else happens after that. This is because we have class App extends PureComponent{} instead of class App extends Component{}.

This solves the problem of components being re-rendered without respect to the current state. However, if we implement a state change within our setState method, we would run into another issue. For example, consider the following changes to setState():

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }
  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: Math.random() };
      });
    }, 2000);
  }

The component will re-render each time the value updates to the next random number, since, as we have discussed, PureComponent makes it possible to re-render components only when there’s been a change in state or props.

Now we can look at how to use React.memo() to achieve the same fix. To do this with React memo, wrap the component with React.memo():

import React, { Component } from "react";

const Unstable = React.memo(function Unstable (props) {
  console.log(" Rendered Unstable component ");
  return <div>{props.val} </div>;
});

class App extends Component {
  state = {
    val: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState({ val: 1 });
    }, 2000);
  }

  render() {
    return (
      <div>
        <header className="App-header">
          <Unstable val={this.state.val} />
        </header>
      </div>
    );
  }
}

export default App;

This achieves the same result as using PureComponent. Hence, the component only renders after the initial render and doesn’t re-render again until there’s a change in state or props. Here’s the Codesandbox for this example.

Conclusion

In this post we have gone through a few ways you can covert an existing class-based component to a functional component using React Hooks. We have also looked at a special case of converting a React PureComponent class to React.memo().

To use Hooks in your applications, be sure to update your version of React to the supported version:

    "react": "^16.7.0-alpha",
    "react-dom": "^16.7.0-alpha",

You now have a foundation to experiment further with React Hooks.

Related Reading:

0 Comments

Creative Commons License