Tutorial

How To Convert a React Class-Based Component to a Functional Component

React

Introduction

Hooks are stable as of React v16.8! The Hooks Proposal is an attempt to address several major concerns developers have with React. Essentially, a Hook is a special function that allows you to “hook into” React features. Hooks are ideal if you’ve previously written a functional component and realized that you need to add state to it.

If you’re new to Hooks and would like an overview, check out our introduction to React Hooks.

useState declares a state variable to preserve values between function calls. The variables are preserved by React. useState only takes one argument that initializes the value of the state that you’re setting. By implementing useState we no longer need this.example, we can access the variable directly.

In this tutorial, you will build upon a previously written class-based component and convert it into a functional component using the useState Hook.

Prerequisites

This tutorial was verified with Node v16.4.0, npm v7.20.3, react v17.0.2, react-dom v17.0.2, react-scripts v4.0.3, bootstrap v4.6.0, and reactstrap v8.9.0.

Step 1 — Setting Up the Project

This tutorial builds upon some starter code. In the starter code, we installed the latest version of react and react-dom as well as reactstrap to help us have some easy formatting.

The component consists of a form with fields for email and password. When the form is submitted, the values are logged to the console.

First, open your terminal and navigate to your working directory. Then clone the repo:

  • git clone https://github.com/do-community/convert-class-to-hook

Next, navigate to the new project directory:

  • cd convert-class-to-hook

Then, install the package dependencies:

  • npm install

Take a moment to familiarize yourself with the current ClassBasedForm.js component located in the components directory.

src/components/ClassBasedForm.js
import React from 'react'
import {
  Form, FormGroup, Input,
  Label, Col, Button,
} from 'reactstrap'

export default class ClassBasedForm extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      email: '',
      password: '',
    }
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault();
    console.log(this.state);
  }

  render() {
    return (
      <Form onSubmit={ this.handleSubmit }>
        <h1>Class-Based Form</h1>
        <FormGroup row>
          <Label for="exampleEmail" sm={ 2 }>Email</Label>
          <Col sm={ 8 }>
            <Input
              type="email"
              name="email"
              id="exampleEmail"
              placeholder="email"
              value={ this.state.email }
              onChange={ (event) => this.setState({ email: event.target.value }) }
            />
          </Col>
        </FormGroup>
        <FormGroup row>
          <Label for="examplePassword" sm={ 2 }>Password</Label>
          <Col sm={ 8 }>
            <Input
              type="password"
              name="password"
              id="examplePassword"
              placeholder="password"
              value={ this.state.password }
              onChange={ (event) => this.setState({ password: event.target.value })}
            />
          </Col>
        </FormGroup>
        <FormGroup check row>
          <Col sm={ { size: 'auto', offset: 8 } }>
            <Button>Submit</Button>
          </Col>
        </FormGroup>
      </Form>
    )
  }
};

Finally, verify your installation by running the application:

  • npm start

Open the application in the browser. Enter values for the email and password and submit the form.

Screenshot of the application running in a browser with the class-based component.

At this point, you have a working class-based component. None of this behavior should change when converting the class-based component to a functional component.

Step 2 — Creating a Functional Component

In this section, you will create an additional Form component in the components folder. This will be a FunctionBasedForm.js file. We’ll use this component to build an identical form using the useState Hook.

Having the class-based component and new functional component side-by-side will allow you to compare the two implementations.

We’ll first import React and create a function variable called FunctionBasedForm that returns some text. Be sure to export this function.

src/components/FunctionBasedForm.js
import React from 'react';

const FunctionBasedForm = () => {
  return (
    <h1>Function Based Form</h1>
  )
};

export default FunctionBasedForm;

Compare this to the class ClassBasedForm extends React.Component { ... } declaration used in ClassBasedForm.js.

Next, you will add your new component to App.js. Open App.js in your code editor.

For now, comment out the previous ClassBasedForm import.

Import the FunctionBasedForm component. Replace your previous component with your new <FunctionBasedForm /> component in the return statement.

src/App.js
import React, { Component } from 'react';
import { Container } from 'reactstrap';
// import ClassBasedForm from './components/ClassBasedForm';
import FunctionBasedForm from './components/FunctionBasedForm';
import Logo from './assets/alligator-logo2.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <img src={ Logo } alt="alligator.io logo" width="200" />
        <Container>
          <FunctionBasedForm />
        </Container>
      </div>
    );
  }
}

export default App;

Save your changes and run the application. Then visit localhost:3000 in the browser:

Screenshot of the application running in the browser with the functional component.

The application will display the text from the new component.

Step 3 — Adding State for email and password

In our original ClassBasedForm.js component we initialize our state using a constructor. We no longer need to initialize state in a typical class component, nor do we for hooks! Let’s consider the following example.

Most of us are familiar with initializing state like so:

constructor(props) {
  super(props);
  this.state = {
    email: '',
    password: '',
  };
}

Since React 16, we no longer need a constructor. The former becomes:

state = {
  email: '',
  password: '',
};

Now in a functional component, we can use hooks. We can initialize state and the related setter at the beginning of our function. This could potentially eliminate several lines of code for us. Here’s how we would initialize state variables in our new component.

const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

Let’s break down one of the lines above.

const [ email, setEmail] = useState('');
  • const: Creates a variable for both our state and the associated state variable setter.
  • email: Initializes and declares our first variable email.
  • setEmail: Initializes the setter function associated with the variable email. Though it may seem redundant useState is only intended to be used for a single value.
  • useState(''): Declares that the variable email starts as an empty string.

Now, revisit FunctionBasedForm.js in your code editor.

Copy code from ClassBasedForm.js and remove the functions, click handlers, and state variables.

Next, add { useState } to your React import. Also, add the state variables defined in the previous section.

src/components/FunctionBasedForm.js
import React, { useState } from 'react';
import {
  Form, FormGroup, Input,
  Label, Col, Button,
} from 'reactstrap'

const FunctionBasedForm = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  return (
    <Form>
      <h1>Function Based Form</h1>
      <FormGroup row>
        <Label for="exampleEmail" sm={ 2 }>Email</Label>
        <Col sm={ 8 }>
          <Input
            type="email"
            name="email"
            id="exampleEmail"
            placeholder="email"
          />
        </Col>
      </FormGroup>
      <FormGroup row>
        <Label for="examplePassword" sm={ 2 }>Password</Label>
        <Col sm={ 8 }>
          <Input
            type="password"
            name="password"
            id="examplePassword"
            placeholder="password"
          />
        </Col>
      </FormGroup>
      <FormGroup check row>
        <Col sm={ { size: 'auto', offset: 8 } }>
          <Button>Submit</Button>
        </Col>
      </FormGroup>
    </Form>
  )
};

export default FunctionBasedForm;

Save your changes and visit the application in your browser.

Screenshot of the application running in the browser with the functional component.

At this point, the functional component is visually identical to the class-based component.

Step 4 — Adding Functionality for onChange and handleSubmit

Now let’s revise our functions to utilize Hooks.

Let’s revisit how we updated state in our class-based component:

onChange={ (event) => this.setState({ email: event.target.value })

With hooks, we no longer need this or this.setState() since we’re already initiating our state variables and attaching a setter.

Since there are two variables, we’re going to use an inline function to call the setter that we initiated in useState for each input. We’ll also add our value back without the this prefix.

For the email field:

<Input
  type="email"
  name="email"
  id="exampleEmail"
  placeholder="email"
  value={ email }
  onChange={ event => setEmail(event.target.value) }
/>

For the password field:

<Input
  type="password"
  name="password"
  id="examplePassword"
  placeholder="password"
  value={ password }
  onChange={ event => setPassword(event.target.value) }
/>

Now let’s rewrite our handleSubmit function.

Here’s how the function was previously written:

handleSubmit(e) {
  e.preventDefault();
  console.log(this.state);
}

Note: In React 16, if you write this as an arrow function, you won’t need to bind it in the constructor.

We now need to create a const for the function. We again prevent the default functionality, set the variables, and console.log them.

const handleSubmit = e => {
  e.preventDefault();
  console.log(email);
  console.log(password);
}

Now we can add our handleSubmit function to the onSubmit in our form.

Here’s how your new functional hook should look:

src/components/FunctionBasedForm.js
import React, { useState } from 'react'
import {
  Form, FormGroup, Input,
  Label, Col, Button,
} from 'reactstrap'

const FunctionBasedForm = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = event => {
    event.preventDefault();
    console.log(email);
    console.log(password);
  }

  return (
    <Form onSubmit={ handleSubmit }>
      <h1>Function Based Form</h1>
      <FormGroup row>
        <Label for="exampleEmail" sm={ 2 }>Email</Label>
        <Col sm={ 8 }>
          <Input
            type="email"
            name="email"
            id="exampleEmail"
            placeholder="email"
            value={ email }
            onChange={ event => setEmail(event.target.value) }
          />
        </Col>
      </FormGroup>
      <FormGroup row>
        <Label for="examplePassword" sm={ 2 }>Password</Label>
        <Col sm={ 8 }>
          <Input
            type="password"
            name="password"
            id="examplePassword"
            placeholder="password"
            value={ password }
            onChange={ event => setPassword(event.target.value) }
          />
        </Col>
      </FormGroup>
      <FormGroup check row>
        <Col sm={ { size: 'auto', offset: 8 } }>
          <Button>Submit</Button>
        </Col>
      </FormGroup>
    </Form>
  )
};

export default FunctionBasedForm;

Save your changes and visit the application in your browser.

Add some values to your form and use the Submit button. Open the developer tools and display the console messages. Values for the email and password will display.

Conclusion

In this tutorial, you built upon a previously written class-based component and converted it into a functional component using the useState Hook.

Continue your learning with How To Apply React Hooks in a React Project.

Creative Commons License