DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Building a Controlled Form using React hooks.
Emilio Quintana
Emilio Quintana

Posted on

Building a Controlled Form using React hooks.

Are you currently remaking your class components into stateless functional components but don't know how to do the same with your controlled forms ? You've come to the right place! In this blog post I will be giving a brief introduction to react hooks and how you can build controlled forms without using state. Let's get started.

What are Hooks ?

According to the React docs:

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

In a few words, hooks are a simpler, more concise way to write components. In order to create a functional component that uses hooks, we will need to use a react function called useState( ). This function allows us to create a variable that will save the state, as well as a function whose only job is to update the state. See below for an example:

import React, { useState } from 'react'

export default function CounterApp() {
    const [count, setCount] = useState(0);
    // variable count mimics this.state.count
    // function setCount mimics this.setState({ count: newCount })

    return <p>{ count }</p>
}

In the example above we created a CounterApp class component that initializes a variable called count to zero and a function called setCount which is responsible for updating the count variable. If we had a class component we would have to initialize that variable within the state of the component and use setState to update it. As you can see, using hooks allows us to achieve the same objective with fewer lines of code.

Now that we got the basics out of the way let's transform a controlled form built using a class component into a functional stateless component using hooks!

Let's start with a class component called SimpleForm with three inputs: name, last name and email address. As you can see below, we have a method called handleChange() that is updating the state every time there is a change within each input.

import React, { Component } from "react";

class SimpleForm extends Component {
  state = {
    name: "",
    lastname: "",
    email: "",
  };

  handleChange = (event) => {
    this.setState({ [event.target.name]: event.target.value });
  };

  render() {
    return (
      <form>
        <label htmlFor="name">
          Name
          <input
            type="text"
            name="name"
            value={this.state.name}
            onChange={this.handleChange}
          />
        </label>
        <label htmlFor="name">
          Lastname
          <input
            type="text"
            name="lastname"
            value={this.state.lastname}
            onChange={this.handleChange}
          />
        </label>
        <label htmlFor="name">
          Email
          <input
            type="text"
            name="email"
            value={this.state.email}
            onChange={this.handleChange}
          />
        </label>
      </form>
    );
  }
}

export default SimpleForm;

Transforming a class components into functional stateless component is a matter of following a couple steps. First, let's replace the current state with hooks.

import React, { Component, useState } from "react";

class SimpleForm extends Component {

    const [name, setName] = useState("");
    const [lastname, setLastname] = useState("");
    const [email, setEmail] = useState("");

  handleChange = (event) => {
    this.setState({ [event.target.name]: event.target.value });
  };

  render() {
    return (
      <form>
        <label htmlFor="name">
          Name
          <input
            type="text"
            name="name"
            value={this.state.name}
            onChange={this.handleChange}
          />
        </label>
        <label htmlFor="name">
          Lastname
          <input
            type="text"
            name="lastname"
            value={this.state.lastname}
            onChange={this.handleChange}
          />
        </label>
        <label htmlFor="name">
          Email
          <input
            type="text"
            name="email"
            value={this.state.email}
            onChange={this.handleChange}
          />
        </label>
      </form>
    );
  }
}

export default SimpleForm;

Great! Now that that's out of the way, let's get rid of all "this" and "this.state" statements.

import React, { Component, useState } from "react";

class SimpleForm extends Component {

    const [name, setName] = useState("");
    const [lastname, setLastname] = useState("");
    const [email, setEmail] = useState("");

  handleChange = (event) => {
        setState({ [event.target.name]: event.target.value });
  };

  render() {
    return (
      <form>
        <label htmlFor="name">
          Name
          <input
            type="text"
            name="name"
            value={name}
            onChange={handleChange}
          />
        </label>
        <label htmlFor="name">
          Lastname
          <input
            type="text"
            name="lastname"
            value={lastname}
            onChange={handleChange}
          />
        </label>
        <label htmlFor="name">
          Email
          <input
            type="text"
            name="email"
            value={email}
            onChange={handleChange}
          />
        </label>
      </form>
    );
  }
}

export default SimpleForm;

Next, we will get rid of the handleChange function since it doesn't make sense to keep it here anymore. Instead we will use the new functions we created to update our variables.

import React, { Component, useState } from "react";

class SimpleForm extends Component {

    const [name, setName] = useState("");
    const [lastname, setLastname] = useState("");
    const [email, setEmail] = useState("");

  render() {
    return (
      <form>
        <label htmlFor="name">
          Name
          <input
            type="text"
            name="name"
            value={name}
            onChange={(event) => setName(event.target.value)} 
          />
        </label>
        <label htmlFor="name">
          Lastname
          <input
            type="text"
            name="lastname"
            value={lastname}
            onChange={(event) => setLastname(event.target.value)}
          />
        </label>
        <label htmlFor="name">
          Email
          <input
            type="text"
            name="email"
            value={email}
            onChange={(event) => setEmail(event.target.value)}
          />
        </label>
      </form>
    );
  }
}

export default SimpleForm;

Almost there. Lastly, we will convert the class component into a functional component by getting rid of the render method and the "class" and "extends Component" keyword.

import React, { useState } from "react";

export default function SimpleForm() {
  const [name, setName] = useState("");
  const [lastname, setLastname] = useState("");
  const [email, setEmail] = useState("");

  return (
    <form>
      <label htmlFor="name">
        Name
        <input
          type="text"
          name="name"
          value={name}
          onChange={(event) => setName(event.target.value)}
        />
      </label>
      <label htmlFor="name">
        Lastname
        <input
          type="text"
          name="lastname"
          value={lastname}
          onChange={(event) => setLastname(event.target.value)}
        />
      </label>
      <label htmlFor="name">
        Email
        <input
          type="text"
          name="email"
          value={email}
          onChange={(event) => setEmail(event.target.value)}
        />
      </label>
    </form>
  );
}

Congratulations! You just built your first controlled form using a stateless functional component and hooks. I hope this helped and happy coding!

Top comments (1)

Collapse
 
3ddpaz profile image
Ed

Isn't better to just use a Reducer ???

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!