Note: These notes are taken from Colt steele’s Modern React Bootcamp course
Forms
- HTML form elements work differently than other DOM elements in React
- Form elements naturally keep some internal state.
- For example, this form in plain HTML accepts a single name:
1<form>
2 <label for="fullname">Full Name:</label>
3 <input name="fullname" />
4 <button>Add!</button>
5</form>
Thinking About State
1<form>
2 <label for="fullname">Full Name:</label>
3 <input name="fullname" />
4 <button>Add!</button>
5</form>
It’s convenient to have a JS function that handles the submission of the form and has access to the data the user entered.
The technique to get this is controlled components.
Controlled Components
- In HTML, form elements such as
<input>
,<textarea>
, and<select>
typically maintain their own state and update it based on user input. - In React, mutable state is kept in the state of components, and only updated with setState().
How do we use React to control form input state?
One Source of Truth
- We make the React state be the “single source of truth”
- React controls:
- What is shown (the value of the component)
- What happens the user types (this gets kept in state)
Input elements controlled in this way are called “controlled components”.
Example Form Component
1class NameForm extends Component {
2 constructor(props) {
3 super(props);
4 // default fullName is an empty string
5 this.state = { fullName: "" };
6 this.handleChange = this.handleChange.bind(this);
7 this.handleSubmit = this.handleSubmit.bind(this);
8 }
9
10 handleSubmit(evt) {
11 // do something with form data
12 }
13 handleChange(evt) {
14 // runs on every keystroke event
15 }
16
17 render() {
18 return (
19 <form onSubmit={this.handleSubmit}>
20 <label htmlFor="fullname">Full Name:</label>
21 <input
22 name="fullname"
23 value={this.state.fullName}
24 onChange={this.handleChange}
25 />
26 <button>Add!</button>
27 </form>
28 );
29 }
30}
How the Controlled Form Works
- Since value attribute is set on element, displayed value will always be
this.state.fullName
— making the React state the source of truth. - Since
handleChange
runs on every keystroke to update the React state, the displayed value will update as the user types. - With a controlled component, every state mutation will have an associated handler function. This makes it easy to modify or validate user input.
handleChange Method
Here is the method that updates state based on input.
1class NameForm extends Component {
2 // ...
3
4 handleChange(evt) {
5 // runs on every keystroke
6 this.setState({
7 fullName: evt.target.value,
8 });
9 }
10
11 // ...
12}
Handling Multiple Inputs
- ES2015 introduced a few object enhancements…
- This includes the ability to create objects with dynamic keys based on JavaScript expressions.
- The feature is called computed property names.
Computed Property Names
ES5
1var catData = {};
2var microchip = 1432345421;
3catData[microchip] = "Blue Steele";
ES2015
1let microchip = 1432345421;
2let catData = {
3 // propery computed inside the object literal
4 [microchip]: "Blue Steele",
5};
Application To React Form Components
Instead of making a separate onChange handler for every single input, we can make one generic function for multiple inputs!
Handling Multiple Inputs
To handle multiple controlled inputs, add the HTML name attribute to each JSX input element and let handler function decide the appropriate key in state to update based on event.target.name.
1class YourComponent extends Component {
2 // ...
3
4 handleChange(evt) {
5 this.setState({
6 [evt.target.name]: evt.target.value,
7 });
8 }
9
10 // ...
11}
Using this method, the keys in state have to match the input name
attributes exactly.
The state:
1this.state = { firstName: "", lastName: "" };
NameForm.js;
1class NameForm extends Component {
2 handleChange(evt) {
3 this.setState({ [evt.target.name]: evt.target.value });
4 }
5
6 render() {
7 return (
8 <form onSubmit={this.handleSubmit}>
9 <label htmlFor="firstName">First:</label>
10 <input
11 id="firstName"
12 name="firstName"
13 value={this.state.firstName}
14 onChange={this.handleChange}
15 />
16
17 <label htmlFor="lastName">Last:</label>
18 <input
19 id="lastName"
20 name="lastName"
21 value={this.state.lastName}
22 onChange={this.handleChange}
23 />
24 <button>Add a new person!</button>
25 </form>
26 );
27 }
28} // end
Design pattern: Passing Data Up to a Parent Component
In React we generally have downward data flow. “Smart” parent components with simpler child components.
- But it is common for form components to manage their own state…
- But the smarter parent component usually has a doSomethingOnSubmit method to update its state after the form submission…
- So what happens is the parent will pass its doSomethingOnSubmit method down as a prop to the child.
- The child component will call this method which will then update the parent’s state.
- The child is still appropriately “dumber”, all it knows is to pass its data into a function it was given.
Shopping List Example
- Parent Component: ShoppingList (manages a list of shopping items)
- Child Component: NewListItemForm (a form to add a new shopping item to the list)
ShoppingList.js
1class ShoppingList extends Component {
2 /* Add new item object to cart. */
3 addItem(item) {
4 let newItem = { ...item, id: uuid() };
5 this.setState((state) => ({
6 items: [...state.items, newItem],
7 }));
8 }
9
10 render() {
11 return (
12 <div className="ShoppingList">
13 <NewListItemForm addItem={this.addItem} />
14 {this.renderItems()}
15 </div>
16 );
17 }
18}
NewListItemForm.js
1class NewListItemForm extends Component {
2 // Send {name, quantity} to parent - & clear form.
3
4 handleSubmit(evt) {
5 evt.preventDefault();
6 this.props.addItem(this.state);
7 this.setState({ name: "", qty: 0 });
8 }
9}
Keys and UUIDs
Using UUID for Unique Keys
- We’ve seen that using an iteration index as a key prop is a bad idea
- No natural unique key? Use a library to create a uuid
- Universally unique identifier (UUID) is a way to uniquely identify info
- Install it using
npm install uuid
Using the UUID Module
ShoppingList.js
1import uuid from "uuid/v4";
ShoppingList.js
1class ShoppingList extends Component {
2 constructor(props) {
3 super(props);
4 this.state = {
5 items: [
6 { name: "Milk", qty: "2 gallons", id: uuid() },
7 { name: "Bread", qty: "2 loaves", id: uuid() },
8 ],
9 };
10 this.addItem = this.addItem.bind(this);
11 }
12 addItem(item) {
13 let newItem = { ...item, id: uuid() };
14 this.setState((st) => ({
15 items: [...st.items, newItem],
16 }));
17 }
18 renderItems() {
19 return (
20 <ul>
21 {this.state.items.map((item) => (
22 <li key={item.id}>
23 {item.name}:{item.qty}
24 </li>
25 ))}
26 </ul>
27 );
28 }
29}
Validation
- Useful for UI
- Not an alternative to server side validation
- Formik