React Cookbook
上QQ阅读APP看书,第一时间看更新

How to do it...

Let's create a new component called Person:

  1. The skeleton we are going to use for this component is as follows:
  import React, { Component } from 'react';
import './Person.css';

class Person extends Component {
constructor() {
super();

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

render() {
return (
<div className="Person">

</div>
);
}
}

export default Person;
File: src/components/Person/Person.js
  1. Let's 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>
);
}
  1. Let's use these CSS styles for our form:
  .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
  1. If you run your application, you should see this view:
  1. Let's use our local state in the inputs. The only way we can retrieve 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>
);
}
If you try to type something, you will notice that you are not allowed to write anything, and 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.
  1. As you can imagine, the only way we can update our local state is by detecting a change in our inputs, and that will happen when the user inputs something. Let's add a method for the onChange event:
  handleOnChange = e => {
const { target: { value } } = e;

this.setState({
firstName: value
});
}
Like I mentioned in the last recipe when we use an arrow function in our methods we are automatically binding the class to the method. Otherwise, you will need to bind the method in the constructor. In our firstName input, we need to call this method on the onChange method:
    <input 
name="firstName"
type="text"
value={this.state.firstName}
onChange={this.handleOnChange}
/>
  1. But here we have a problem. If we have four fields, then you will probably think you need to create four different methods (one for each state), but there is a better way to solve this: to get the value of the input name within the e (e.target.name) object. In this way, we can update all the states with the same method. Our handleOnChange method should now look like this:
    handleOnChange = e => {
const { target: { value, name } } = e;

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

  1. With this ([name]) syntax in the object, we can update all the states we have in our forms dynamically. Now we need to add this method to the onChange of all the inputs. After this, you will be able to write into 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>
);
}
  1. All forms need to submit the information they have collected from the user. We need to use the onSubmit event of our form and call a handleOnSubmit method to retrieve all the input 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);
}
  1. After we created this method we need to call it on the onSubmit event of the form tag:
  <form onSubmit={this.handleOnSubmit}>
  1. Now you can test this. Open your browser console, and when you write some values in the inputs you will be able to see the data:
  1. We need to validate the required fields. Let's suppose that the firstName and lastName fields are mandatory. If a user doesn't 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:
      this.state = {
firstName: '',
lastName: '',
email: '',
phone: '',
errors: {
firstName: false,
lastName: false
}
};
  1. You can add any fields you want to validate here, 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
// event won't occur.
e.preventDefault();

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

// If firstName or lastName is 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);
}
  1. Now, in your render method you need to add a ternary validation in the className prop of the firstName and lastName fields, and if you want to be fancy you can also add an error message below the inputs:
    render() {
return (
<div className="Person">
<form onSubmit={this.handleOnSubmit}>
<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>

<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>
);
}
  1. The last step is to add the error classes, .error and .errorMessage:
    .Person .error {
border: 1px solid red;
}

.Person .errorMessage {
color: red;
font-size: 10px;
}
  1. If you submit your form without firstName or lastName now, you will get this view:

  1. The full Person component should be like this:
  import React, { Component } from 'react';
import './Person.css';

class Person extends Component {
constructor() {
super();

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

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

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

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 is missing we add an error class
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);
}

render() {
return (
<div className="Person">
<form onSubmit={this.handleOnSubmit}>
<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>

<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>
);
}
}

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