Forms in React
Controlled Components
We want the value of our input boxes to always be current with state
. We do that with adding the event handler "onChange" to our inputs and then handling that change by updating state
.
class NameForm extends React.Component {
//here is a pretty standard constructor
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
//handleChange does not need to be complicated. We are only mirroring what's in the input box (event.target.value) to the state.
handleChange(event) {
this.setState({value: event.target.value});
}
//we need the event in the handle submit so we can prevent the submission from refreshing the page with .preventDefault()
handleSubmit(event) {
alert('A name was submitted: ' + 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>
);
}
}
handleChange()
takes an event object. If this is new to you, you can learn about it here. This article also goes into the event.preventDefault()
Look at our input box. Not only do we handle every change and update state with the onChange
event listener, but we keep the value
up to date too.
Use Controlled Components in React. You can make things work without it, but it's not good to do.
Vocabulary
preventDefault() - prevents submission of a form (either pressing enter or clicking the submit button in a form) from refreshing the page.
Controlled Component - a component where state keeps current with any user input.
The select Tag
In HTML, <select>
creates a drop-down list. For example, this HTML creates a drop-down list of flavors:
<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 initially selected, because of the selected attribute. React, instead of using this selected attribute, React uses a value attribute on the root select tag. This is more convenient in a controlled component because you only need to update it in in the <select>
tag itself instead of in one of the <option>
tags. 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>
Pick your favorite La Croix 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 it so that <input type="text">
, <textarea>
, and <select>
all work very similarly - they all accept a value attribute that you can use to implement a controlled component.
Handling Multiple Inputs
When you need to handle multiple controlled input elements, you can add a name attribute to each element and let the handler function choose what to do based on the value of event.target.name
. We don't want to have to write a handleInputChange
function for every input box, so we can make our function dynamic by using the "name" attribute.
For example:
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.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is 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>
);
}
}
Note how we used the ES6 computed property name syntax to update the state key corresponding to the given input name:
this.setState({
[name]: value
});
It is equivalent to this ES5 code:
var partialState = {};
partialState[name] = value;
this.setState(partialState);
When we change the "numberOfGuests" input box, it updates this.state.numberOfGuest
, and when we change the "isGoing" checkbox, it updates this.state.isGoing
.
Make sure you understand all the pieces here. Ask questions, play with this code, break it and fix it again. You'll be writing code like this all the time in React.
Also, since setState()
automatically merges a partial state into the current state, we only needed to call it with the changed parts.