In this article, you will learn how to use a form in React and how to handle text inputs, radio buttons, checkboxes, selects and other input types.

Web applications often need to send data from browser to the backend server. Certainly, the most used way of doing so is through a HTML form, by using text inputs, radio buttons, checkboxes, selects and so on. This remains true in React. Are you looking how to handle forms in React? If so, this article is a great fit for you. Have a good read.

Controlled vs Uncontrolled components

In React, when a component renders a form with elements such as text inputs, selects or checkboxes, there are two ways of keeping and updating their state:

  1. The state is kept and updated by the input itself. This is the way forms work in pure HTML.
  2. The state is handled by the component that renders the form. It uses the component’s state for such purpose and leverages event handlers to detect changes and keep the state up-to-date.** This is the recommended way of handling forms in React.**

In the first approach, React has no control over the input’s state. Therefore, these form inputs are called uncontrolled components.

In the second approach, however, React controls the input’s state. For this reason, such inputs are called controlled components.

As controlled components are the way to go in React, I won’t cover uncontrolled components in this article.

Text inputs

You need to use the value attribute and the onChange event in order to handle text inputs. Check out this example:

export default class App extends Component {
  state = { name: '' };
  
  handleNameChange = (e) => {/*...*/}
  
  render() {
    return (
      <div>
        <form>
          <label>Name:</label>
          <input value={this.state.name} onChange={this.handleNameChange} type="text" />
        </form>
      </div>
    );
  }
}

In the above component, every time a change occurs in our input, the handleNameChange event handler is called. Let’s take a look at it:

handleNameChange = (e) => {
  const name = e.target.value;
  this.setState({ name });
};

Well, there’s nothing special here. This handler receives an event object that holds, among other things, the up-to-date value of the input. After getting it, this same value is assigned to its matching entry in the component’s state by using the setState method.

After the change is performed, the value attribute of the input is automatically updated, since the this.state.name state entry is bound to it. To make it more visible, you can update your code to print the input value:

render() {
  return (
    <div>
      <form>
        <label>Name:</label>
        <input value={this.state.name} onChange={this.handleNameChange} type="text" />
      </form>
      <strong>The typed name is: </strong><span>{this.state.name}</span>
    </div>
  );
}

Let’s use the React Developer Tools extension to see how it behaves:

As you can see, every time a change happens, the component’s state is updated and, as a consequence, the value attribute of the input too.

Textareas

In pure HTML, a textarea has no value attribute. Its value is defined by its content. In React, however, textareas and common text inputs are handled in a very similar way:

export default class App extends Component {
  state = { bio: '' };

  handleBioChange = (e) => {
    const bio = e.target.value;
    this.setState({ bio });
  };

  render() {
    return (
      <form>
        <label>Bio:</label>
        <textarea value={this.state.bio} onChange={this.handleBioChange} />
      </form>
    );
  }
}

As you may have noticed, the above code is almost identical to the text input example. In both, we use the onChange event and the value attribute to keep the component’s and input’s states synchronized.

Selects

Similarly to textareas, select tags also have no value attribute in HTML. However, their children – option tags – do have it. To set the value of a select tag, you’d need to add a selected attribute to one of its options. React favors a simpler approach, by bringing the simplicity of the value attribute to select tags:

render() {
  return (
    <form>
      <label>Desired color:</label>
      <select value={this.state.color} onChange={this.handleColorChange}>
        <option></option>
        <option value="blue">Blue</option>
        <option value="red">Red</option>
        <option value="green">Green</option>
        <option value="yellow">Yellow</option>
      </select>
    </form>
  );
}

The handleColorChange event handler has nothing new:

handleColorChange = (e) => {
  const color = e.target.value;
  this.setState({ color });
};

If you want your dropdown to have multiple values selected, set the multiple attribute to true and assign an array as its value, like this:

render() {
  return (
    <form>
      <label>Desired color:</label>
      <select multiple={true} value={['blue', 'red']}>
        <option></option>
        <option value="blue">Blue</option>
        <option value="red">Red</option>
        <option value="green">Green</option>
        <option value="yellow">Yellow</option>
      </select>
    </form>
  );
}

Checkboxes

**Checkboxes **use the checked attribute instead of the value one. Both work in a very similar manner, but the value of the checked attribute must be a boolean. Check out this example:

state = { acceptedAgreement: false };

handleAcceptAgreementChange = (e) => {
  const acceptedAgreement = e.target.checked;
  this.setState({ acceptedAgreement });
};

render() {
  return (
    <form>
      <label>
        I accept the agreement
        <input
          checked={this.state.acceptedAgreement}
          onChange={this.handleAcceptAgreementChange}
          type="checkbox" />
      </label>
    </form>
  );
}

Let’s see how it works under the hood:

Radio buttons

Radio buttons are treated differently in React. The most common way of handling a group of radio buttons is by using both value and checked attributes together:

<form>
  <label>
    Beginner
    <input
      value="BEGINNER"
      checked={this.state.skillLevel === 'BEGINNER'}
      onChange={this.handleSkillLevelChange}
      type="radio" />
  </label>
  <label>
    Intermediate
    <input
      value="INTERMEDIATE"
      checked={this.state.skillLevel === 'INTERMEDIATE'}
      onChange={this.handleSkillLevelChange}
      type="radio" />
  </label>
  <label>
    Advanced
    <input
      value="ADVANCED"
      checked={this.state.skillLevel === 'ADVANCED'}
      onChange={this.handleSkillLevelChange}
      type="radio" />
  </label>
</form>

In traditional HTML, in order to create a group of radio buttons, you need to use the name attribute. As you can see in the above code, we don’t need to do this in React. This is because the this.state.skillLevel === […] condition in each checked attribute implements the needed grouping logic, once it won’t be possible to check two radio buttons at the same time.

Here’s the handleSkillLevelChange event handler (it’s identical to the above examples):

handleSkillLevelChange = (e) => {
  const skillLevel = e.target.value;
  this.setState({ skillLevel });
};

As you may have noticed, there’s a lot of repeated code in the render method of this component. You could make it shorter by creating an array of options and using the map method to render each option as a separate radio button.

render() {
  const levels = ['Beginner', 'Intermediate', 'Advanced'];
  return (
    <form>
      {levels.map((level, index) =>
        <label key={index}>
          {level}
          <input
            value={level.toUpperCase()}
            checked={this.state.skillLevel === level.toUpperCase()}
            onChange={this.handleSkillLevelChange}
            type="radio" />
        </label>
      )}
    </form>
  );
}

Forms with multiple inputs

To put it all together, let’s handle a form with multiple inputs:

render() {
  const colors = ['Blue', 'Red', 'Green', 'Yellow'];
  const sizes = ['Small', 'Medium', 'Large', 'XL', 'XXL', '3XL'];
  
  return (
    <form>
      <ul>
        <li>
          <label>Name:</label>
          <input name="name" type="text" value={this.state.name} onChange={this.handleChanges} />
        </li>
        <li>
          <label>Observation:</label>
          <textarea name="observation" value={this.state.observation} onChange={this.handleChanges} />
        </li>
        <li>
          <label>Desired color:</label>
          <select name="color" value={this.state.color} onChange={this.handleChanges}>
            {colors.map((color, i) => <option key={i} value={color.toLowerCase()}>{color}</option>)}
          </select>
        </li>
        <li>
          <label>T-shirt Size:</label>
          {sizes.map((size, i) =>
            <label key={i}>
              {size}
              <input
                name="size"
                value={size.toUpperCase()}
                checked={this.state.size === size.toUpperCase()}
                onChange={this.handleChanges}
                type="radio" />
            </label>
          )}
        </li>
        <li>
          <label>
            I accept the agreement
            <input name="acceptedAgreement" type="checkbox" value={this.state.acceptAgreement} onChange={this.handleChanges} />
          </label>
        </li>
      </ul>
    </form>
  );
}

In the above form, we’re using all input types we’ve seen so far. Note that I also added a name attribute to each input, so it will be a lot easier to distinguish between them.

This is how our state object looks like:

state = {
  name: '',
  observation: '',
  color: '',
  size: '',
  acceptedAgreement: false
};

And finally, here’s the final version of the handleChanges event handler:

handleChanges = (e) => {
  const input = e.target;
  const name = input.name;
  const value = input.type === 'checkbox' ? input.checked : input.value;
  this.setState({ [name]: value });
};

As you can see, I use the name property to know which input has triggered the event. I also test if the input is a checkbox. If it is, I get its value through the checked property, if not, I use the value property.

Check out how it works:

Handling the form submission

Finally, to handle the form submission, you just need to define a handler for the onSubmit event:

handleFormSubmit = (event) => {
  console.log(this.state.name)
  event.preventDefault();
};

render() {
  return (
    <form onSubmit={this.handleFormSubmit}>
      <label>Name:</label>
      <input type="text" value={this.state.name} onChange={this.handleNameChange} />
      <button type="submit">Send</button>
    </form>
  );
}

In this event handler, you could submit the values of your form to an endpoint by using, for example, the fetch API. You also need to call the event.preventDefault method to prevent a traditional form submission, which reloads the entire page.

What about file inputs?

As the value of a file input is read-only, this type of input can’t be controlled by React, therefore, file inputs will always be uncontrolled. If you want to implement file upload in React, take a look at this article.

Conclusion

  • An input’s state can be **handled by React **(controlled component) or by itself (uncontrolled component).
  • Controlled components are the recommended way of handling forms in React.

#reactjs #web-development

The complete guide Handling a Forms in React 🚀🚀🚀
2 Likes28.65 GEEK