Dev

By Carlos Santana on
Reading time: 3 minutes

Forms-kBp7z.png

If you read our last post Binding methods: class constructor vs arrow functions probably you noticed that we used basic inputs with some events. In this post, we will go deeper in this topic.

Creating forms

In order to explain this topic, we will create a component called Person. This is the skeleton of the component:

import React, { Component } from 'react';
import './Person.css';

class Person extends Component {
  state = {
    firstName: '',
    lastName: '',
    email: '',
    phone: ''
  };
  
  render() {
    return (
      <div className="Person">

      </div>
    );
  }
}

export default Person;
File: src/components/Person/Person.js

First, we need to add the firstName, lastName, email, and phone fields to our form. The render method should look like this: 

render() {
  return (
    <div className="Person">
      <form>
        <div>
          <p><strong>First Name:</strong></p>
          <p><input name="firstName" type="text" /></p>
        </div>

        <div>
          <p><strong>Last Name:</strong></p>
          <p><input name="lastName" type="text" /></p>
        </div>

        <div>
          <p><strong>Email:</strong></p>
          <p><input name="email" type="email" /></p>
        </div>

        <div>
          <p><strong>Phone:</strong></p>
          <p><input name="phone" type="tel" /></p>
        </div>

        <p>
          <button>Save Information</button>
        </p> 
      </form>
    </div>
  );
}

Let's add some basic styles:

.Person {
  margin: 0 auto;
}

.Person form input {
  font-size: 16px;
  height: 50px;
  width: 300px;
}

.Person form button {
  background: #0072ff;
  border: none;
  color: #fff;
  font-size: 16px;
  height: 50px;
  width: 300px;
}
File: src/components/Person/Person.css

If you run the application, you will see something like this:

 

PersonComponent-Kg534.png

Connecting local state to the inputs

The only way we have to get the values from the inputs in React is by connecting the value of each field to a specific local state like this: 

render() {
  return (
    <div className="Person">
      <form>
        <div>
          <p><strong>First Name:</strong></p>
          <p><input name="firstName" type="text" value={this.state.firstName} /></p>
        </div>

        <div>
          <p><strong>Last Name:</strong></p>
          <p><input name="lastName" type="text" value={this.state.lastName} /></p>
        </div>

        <div>
          <p><strong>Email:</strong></p>
          <p><input name="email" type="email" value={this.state.email} /></p>
        </div>

        <div>
          <p><strong>Phone:</strong></p>
          <p><input name="phone" type="tel" value={this.state.phone} /></p>
        </div>

        <p>
          <button>Save Information</button>
        </p>
      </form>
    </div>
  );
}

TIP: If you try to type in the inputs, you will notice that you are not allowed to write anything, this is because all the inputs are connected to the local state, and the only way we can re-render the typed text is by updating the local state.

As you can imagine, the only way to update our local state is by detecting a change in our inputs, and this will happen when the user types something. We have to add a method for the onChange event: 

handleOnChange = e => {
  const { target: { value } } = e;

  this.setState({
    firstName: value
  });
}

As I mentioned in my last post Binding methods: class constructor vs arrow functions when we use an arrow function in our methods we are automatically binding the class object (this) to the method. Otherwise, you will need to bind the method using the constructor.

In our firstName input, we need to add the method onChange: 

  <input 
    name="firstName" 
    type="text" 
    value={this.state.firstName}
    onChange={this.handleOnChange}
  />

There is a problem with this approach. If we have multiple fields, then you probably think you will need to create one method for each state, but there is a better way to solve this: getting the value of the input name with the event (e) object. Using this approach, we can update all the states with the same method. Let's change a little bit our handleOnChange method:

handleOnChange = e => {
  const { target: { value, name } } = e;

  this.setState({
    [name]: value
  });
}

The [name] syntax in the object helps us to update all the states we have in our forms dynamically. Now we need to add this method to the onChange of all inputs, and after this, you will be able to type in the inputs.

render() {
  return (
    <div className="Person">
      <form>
        <div>
          <p><strong>First Name:</strong></p>
          <p>
            <input
              name="firstName"
              type="text"
              value={this.state.firstName}
              onChange={this.handleOnChange}
            />
          </p>
        </div>

        <div>
          <p><strong>Last Name:</strong></p>
          <p>
            <input
              name="lastName"
              type="text"
              value={this.state.lastName}
              onChange={this.handleOnChange}
            />
          </p>
        </div>

        <div>
          <p><strong>Email:</strong></p>
          <p>
            <input
              name="email"
              type="email"
              value={this.state.email}
              onChange={this.handleOnChange}
            />
          </p>
        </div>

        <div>
          <p><strong>Phone:</strong></p>
          <p>
            <input
              name="phone"
              type="tel"
              value={this.state.phone}
              onChange={this.handleOnChange}
            />
          </p>
        </div>

        <p>
          <button>Save Information</button>
        </p>
      </form>
    </div>
  );
}

Submitting data

All forms need to submit the information they have collected from the user. You have to use the onSubmit event in the form tag and call to handleOnSubmit method to retrieve all the inputs values through the local state.

handleOnSubmit = e => {
  // The e.preventDefault() method cancels the event if it is cancelable,
  // meaning that the default action that belongs to the event won't occur.
  e.preventDefault();

  const { firstName, lastName, email, phone } = this.state;

  const data = {
    firstName,
    lastName,
    email,
    phone
  };

  // Once we have the data collected we can call a Redux Action
  // or process the data as we need it.
  console.log('Data:', data);
}

After you added this method to the code, you need to add the onSubmit prop in the form tag. 

<form onSubmit={this.handleOnSubmit}>

Great, now we can test this component, just open your browser console and then write some data in the inputs and click on the Save Information button. You will be able to see the data you have sent in the inputs.

PersonData-FvT2m.png

Validating required fields

Let's suppose that firstName and lastName fields are mandatory in our form. If a user does not write a value in the fields, we want to add an error class to display a red border around the input. The first thing you need to do is to add a new local state for errors:

state = {
  firstName: '',
  lastName: '',
  email: '',
  phone: '',
  errors: {
    firstName: false,
    lastName: false
  }
};

You can add any fields you want to validate in that state, and the value is boolean (true means there is an error, false means it is okay). Then, in the handleOnSubmit method, we need to update the state if we have an error: 

handleOnSubmit = e => {
  // The e.preventDefault() method cancels the event if it is cancelable,
  // meaning that the default action that belongs to the event won't occur.
  e.preventDefault();

  const { firstName, lastName, email, phone } = this.state;

  // If firstName or lastName are missing then we update the
  // local state with true
  this.setState({
    errors: {
      firstName: firstName === '',
      lastName: lastName === ''
    }
  });

  const data = {
    firstName,
    lastName,
    email,
    phone
  };

  // Once we have the data collected we can call a Redux Action
  // or process the data as we need it.
  console.log('Data:', data);
}

After this, we need to add a validation in the render method to add (or not) a className for the firstName and lastName fields, and if you want to be fancy you can also add an error message below the inputs like this: 

<div>
  <p><strong>First Name:</strong></p>
  <p>
    <input
      name="firstName"
      type="text"
      value={this.state.firstName}
      onChange={this.handleOnChange}
      className={this.state.errors.firstName ? 'error' : ''}
    />
    {this.state.errors.firstName && (
      <div className="errorMessage">Required field</div>
    )}
  </p>
</div>

<div>
  <p><strong>Last Name:</strong></p>
  <p>
    <input
      name="lastName"
      type="text"
      value={this.state.lastName}
      onChange={this.handleOnChange}
      className={this.state.errors.lastName ? 'error' : ''}
    />
    {this.state.errors.lastName && (
      <div className="errorMessage">Required field</div>
    )}
  </p>
</div>

The last step is to add the error classes in the CSS file: 

Only members can see all the codes
You can Login or Sign Up

File: src/components/Person/Person.css

Testing time!, let's click on the Save Information button without writing anything on first and last name fields: 

PersonError-Hu28n.png

Summary

The forms are very important for any web application, and handling them with React is easy by using local state, but is not the only way to handle them. If your forms are complex, with multiple steps (typically used on user registration), you probably need to keep the values throughout the entire process. In this scenario, it is painless to handle forms using Redux Form, I'm going to talk about that in the next posts so stay tuned.

If you like what you have read and you want to learn more about React, then you should get my React Cookbook, right now have a discount on the Packt's official site.

avatarLeave a comment

Your comment

Only members can comment. You can Login or Sign Up