Source: https://react.docschina.org/docs/forms.html
1. Forms#
In React, the way HTML form elements work is different from other DOM elements because forms typically maintain some internal state
. For example, the following pure HTML form only accepts a name:
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
This form has the default HTML form behavior, which automatically refreshes the page after the user submits the form. If you execute the same code in React, it still works.
However, in most cases, using JavaScript functions can conveniently handle form submissions while also accessing the data filled in by the user. The standard way to achieve this effect is to use "controlled components."
Controlled Components#
In HTML, form elements like <input>
, <textarea>
, and <select>
typically maintain their own state
and update based on user input. In React, mutable state is usually stored in the component's state property and can only be updated using setState()
.
We can combine the two, making React's state the "single source of truth." The React component rendering the form also controls the operations that occur during user input. Form input elements controlled in this way by React are called "controlled components."
For example, if we want to print the name upon submission in the previous example, we can write the form as a controlled component:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Submitted name: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Since the value
attribute is set on the form element, the displayed value will always be this.state.value
, making React's state the single source of truth. Because handleChange
executes on every key press and updates React's state, the displayed value will update as the user types.
For controlled components, the input value is always driven by React's state. You can also pass the value to other UI elements or reset it through other event handler functions, but this means you need to write more code.
textarea Tag#
In HTML, the <textarea>
element defines its text through its child elements:
<textarea>
Test!
</textarea>
In React, <textarea>
uses the value
attribute instead. This makes forms using <textarea>
very similar to those using single-line inputs:
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Submitted essay: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Note that this.state.value
is initialized in the constructor, so the text area has a default value.
select Tag#
In HTML, <select>
creates a dropdown list. For example, the following HTML creates a dropdown list related to fruits:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
Note that the coconut option is selected by default due to the selected
attribute. React does not use the selected
attribute; instead, it uses the value
attribute on the root select
tag. This is more convenient in controlled components because you only need to update it in the root tag. For example:
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Choose your favorite flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Overall, this makes tags like <input type="text">
, <textarea>
, and <select>
very similar—they all accept a value attribute that you can use to implement controlled components.
Note: You can pass an array to the value attribute to support selecting multiple options in the select tag:
<select multiple={true} value={['B', 'C']}>
File Input Tag#
In HTML, <input type="file">
allows users to select one or more files from their storage devices to upload to a server or control through JavaScript's File API
.
<input type="file" />
Because its value is read-only, it is an uncontrolled component in React.
Handling Multiple Inputs#
When handling multiple input
elements, we can add a name
attribute to each element and let the handler choose the operation to execute based on event.target.name
.
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.name === 'isGoing' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of Guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
Here, the ES6 computed property name syntax is used to update the state value corresponding to the given input name:
For example:
this.setState({
[name]: value
});
Is equivalent to ES5:
var partialState = {};
partialState[name] = value;
this.setState(partialState);
Additionally, since setState()
automatically merges partial state into the current state, you only need to call it to change part of the state.
Controlled Input Null Value#
Specifying the value prop on a controlled component will prevent the user from changing the input. If you specify a value but the input is still editable, it may be that you accidentally set the value to undefined
or null
.
The following code demonstrates this. (The input is initially locked but becomes editable after a short delay.)
ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
Alternatives to Controlled Components#
Sometimes using controlled components can be cumbersome because you need to write event handler functions for every way data can change and pass all input state through a React component. This can be tedious when converting a previous codebase to React or integrating a React application with a non-React library. In these cases, you might want to use uncontrolled components, which is another way to implement input forms.
Mature Solutions#
If you are looking for a complete solution that includes validation, tracking accessed fields, and handling form submissions, using Formik is a good choice. However, it is also built on controlled components and managing state—so don't overlook learning them.