Forms
Forms 101
Forms are one of the most crucial parts of our applications. While we get some interaction through clicks and mouse moves, it's really through forms where we'll get the majority of our rich input from our users.
In a sense, it's where the rubber meets the road. It's through a form that a user can add their payment info, search for results, edit their profile, upload a photo, or send a message. Forms transform your web site into a web app.
Forms can be deceptively simple. All you really need are some input
tags and a submit
tag wrapped up in a form
tag. However, creating a rich, interactive, easy to use form can often involve a significant amount of programming:
- Form inputs modify data, both on the page and the server.
- Changes often have to be kept in sync with data elsewhere on the page.
- Users can enter unpredictable values, some that we'll want to modify or reject outright.
- The UI needs to clearly state expectations and errors in the case of validation failures.
- Fields can depend on each other and have complex logic.
- Data collected in forms is often sent asynchronously to a back-end server, and we need to keep the user informed of what's happening.
- We want to be able to test our forms.
If this sounds daunting, don't worry! This is exactly why React was created: to handle the complicated forms that needed to be built at Facebook.
We're going to explore how to handle these challenges with React by building a sign up app. We'll start simple and add more functionality in each step.
Preparation
Inside the code download that came with this book, navigate to forms
:
$ cd forms
This folder contains all the code examples for this chapter. To view them in your browser install the dependencies by running npm install
(or npm i
for short):
$ npm i
Once that's finished, you can start the app with npm start
:
$ npm start
You should expect to see the following in your terminal:
$ npm start
Compiled successfully!
The app is running at:
http://localhost:3000/
You should now be able to see the app in your browser if you go to http://localhost:3000
.
This app is powered by Create React App, which we cover in the next chapter.
The Basic Button
At their core, forms are a conversation with the user. Fields are the app's questions, and the values that the user inputs are the answers.
Let's ask the user what they think of React.
We could present the user with a text box, but we'll start even simpler. In this example, we'll constrain the response to just one of two possible answers. We want to know whether the user thinks React is either "great" or "amazing", and the simplest way to do that is to give them two buttons to choose from.
Here's what the first example looks like:
To get our app to this stage we create a component with a render()
method that returns a div
with three child elements: an h1
with the question, and two button
elements for the answers. This will look like the following:
render() {
return (
<div>
<h1>What do you think of React?</h1>
<button
name='button-1'
value='great'
onClick={this.onGreatClick}
>
Great
</button>
<button
name='button-2'
value='amazing'
onClick={this.onAmazingClick}
>
Amazing
</button>
</div>
);
}
So far this looks a lot like how you'd handle a form with vanilla HTML. The important part to pay attention to is the onClick
prop
of the button
elements. When a button
is clicked, if it has a function set as its onClick
prop
, that function will be called. We'll use this behavior to know what our user's answer is.
To know what our user's answer is, we pass a different function to each button. Specifically, we'll create function onGreatClick()
and provide it to the "Great" button and create function onAmazingClick()
and provide it to the "Amazing" button.
Here's what those functions look like:
onGreatClick = (evt) => {
console.log('The user clicked button-1: great', evt);
};
onAmazingClick = (evt) => {
console.log('The user clicked button-2: amazing', evt);
};
When the user clicks on the "Amazing" button, the associated onClick
function will run (onAmazingClick()
in this case). If, instead, the user clicked the "Great" button, onGreatClick()
would be run instead.
Notice that in the
onClick
handler we passthis.onGreatClick
and notthis.onGreatClick()
.What's the difference?
In the first case (without parens), we're passing the function
onGreatClick
, whereas in the second case we're passing the result of calling the functiononGreatClick
(which isn't what we want right now).
This becomes the foundation of our app's ability to respond to a user's input. Our app can do different things depending on the user's response. In this case, we log different messages to the console
.
Events and Event Handlers
Note that our onClick
functions (onAmazingClick()
and onGreatClick()
) accept an argument, evt
. This is because these functions are event handlers.
Handling events is central to working with forms in React. When we provide a function to an element's onClick
prop
, that function becomes an event handler. The function will be called when that event occurs, and it will receive an event object as its argument.
In the above example, when the button
element is clicked, the corresponding event handler function is called (onAmazingClick()
or onGreatClick()
) and it is provided with a mouse click event object (evt
in this case). This object is a SyntheticMouseEvent
. This SyntheticMouseEvent
is just a cross-browser wrapper around the browser's native MouseEvent
, and you'll be able to use it the same way you would a native DOM event. In addition, if you need the original native event you can access it via the nativeEvent
attribute (e.g. evt.nativeEvent
).
Event objects contain lots of useful information about the action that occurred. A MouseEvent
for example, will let you see the x and y coordinates of the mouse at the time of the click, whether or not the shift key was pressed, and (most useful for this example) a reference to the element that was clicked. We'll use this information to simplify things in the next section.
Instead, if we were interested in mouse movement, we could have created an event handler and provided it to the
onMouseMove
prop
. In fact, there are many such elementprops
that you can provide mouse event handlers to:,onClick
,onContextMenu
,onDoubleClick
,onDrag
,onDragEnd
,onDragEnter
,onDragExit
,onDragLeave
,onDragOver
,onDragStart
,onDrop
,onMouseDown
,onMouseEnter
,onMouseLeave
,onMouseMove
,onMouseOut
,onMouseOver
, andonMouseUp
.And those are only the mouse events. There are also clipboard, composition, keyboard, focus, form, selection, touch, ui, wheel, media, image, animation, and transition event groups. Each group has its own types of events, and not all events are appropriate for all elements. For example, here we will mainly work with the form events,
onChange
andonSubmit
, which are related toform
andinput
elements.For more information on events in React, see React's documentation on the Event System.
Back to the Button
In the previous section, we were able to perform different actions (log different messages) depending on the action of the user. However, the way that we set it up, we'd need to create a separate function for each action. Instead, it would be much cleaner if we provided the same event handler to both buttons, and used information from the event itself to determine our response.
To do this, we replace the two event handlers onGreatClick()
and onAmazingClick()
with a new single event handler, onButtonClick()
.
onButtonClick = (evt) => {
const btn = evt.target;
console.log(`The user clicked ${btn.name}: ${btn.value}`);
};
Our click handler function receives an event object, evt
. evt
has an attribute target
that is a reference to the button that the user clicked. This way we can access the button that the user clicked without creating a function for each button. We can then log out different messages for different user behavior.
Next we update our render()
function so that our button
elements both use the same event handler, our new onButtonClick()
function.
render() {
return (
<div>
<h1>What do you think of React?</h1>
<button
name='button-1'
value='great'
onClick={this.onButtonClick}
>
Great
</button>
<button
name='button-2'
value='amazing'
onClick={this.onButtonClick}
>
Amazing
</button>
</div>
);
}
By taking advantage of the event object and using a shared event handler, we could add 100 new buttons, and we wouldn't have to make any other changes to our app.
Text Input
In the previous example, we constrained our user's response to only one of two possibilities. Now that we know how to take advantage of event objects and handlers in React, we're going to accept a much wider range of responses and move on to a more typical use of forms: text input.
To showcase text input we'll create a "sign-up sheet" app. The purpose of this app is to allow a user to record a list of names of people who want to sign up for an event.
The app presents the user a text field where they can input a name and hit "Submit". When they enter a name, it is added to a list, that list is displayed, and the text box is cleared so they can enter a new name.
Here's what it will look like:
Accessing User Input With refs
We want to be able to read the contents of the text field when the user submits the form. A simple way to do this is to wait until the user submits the form, find the text field in the DOM, and finally grab its value
.
To begin we'll start by creating a form element with two child elements: a text input field and a submit button:
render() {
return (
<div>
<h1>Sign Up Sheet</h1>
<form onSubmit={this.onFormSubmit}>
<input
placeholder='Name'
ref='name'
/>
<input type='submit' />
</form>
</div>
);
}
This is very similar to the previous example, but instead of two button
elements, we now have a form
element with two child elements: a text field and a submit button.
There are two things to notice. First, we've added an onSubmit
event handler to the form
element. Second, we've given the text field a ref
prop
of 'name'
.
By using an onSubmit
event handler on the form
element this example will behave a little differently than before. One change is that the handler will be called either by clicking the "Submit" button, or by pressing "enter"/"return" while the form
has focus. This is more user-friendly than forcing the user to click the "Submit" button.
However, because our event handler is tied to the form
, the event object argument to the handler is less useful than it was in the previous example. Before, we were able to use the target
prop
of the event to reference the button
and get its value. This time, we're interested in the text field's value. One option would be to use the event's target
to reference the form
and from there we could find the child input
we're interested in, but there's a simpler way.
In React, if we want to easily access a DOM element in a component we can use refs
(references). Above, we gave our text field a ref
property of 'name'
. Later when the onSubmit
handler is called, we have the ability to access this.refs.name
to get a reference to that text field. Here's what that looks like in our onFormSubmit()
event handler:
onFormSubmit = (evt) => {
evt.preventDefault();
console.log(this.refs.name.value);
};
Use
preventDefault()
with theonSubmit
handler to prevent the browser's default action of submitting the form.
As you can see, by using this.refs.name
we gain a reference to our text field element and we can access its value
property. That value
property contains the text that was entered into the field.
With just the two functions render()
and onFormSubmit()
, we should now be able to see the value
of the text field in our console when we click "Submit". In the next step we'll take that value
and display it on the page.
Using User Input
Now that we've shown that we can get user submitted names, we can begin to use this information to change the app's state and UI.
The goal of this example is to show a list with all of the names that the user has entered. React makes this easy. We will have an array in our state to hold the names, and in render()
we will use that array to populate a list.
When our app loads, the array will be empty, and each time the user submits a new name, we will add it to the array. To do this, we'll make a few additions to our component.
First, we'll create a names
array in our state. In React, when we're using ES6 component classes we can set the initial value of our state
object by defining a property of state
.
Here's what that looks like:
module.exports = class extends React.Component {
static displayName = "04-basic-input";
state = { names: [] }; // <-- initial state
static
belongs to the classNotice in this component we have the line:
static displayName = "04-basic-input";
This means that this component class has a static property
displayName
. When a property is static, that means it is a class property (instead of an instance property). In this case, we're going to use thisdisplayName
when we show the list of examples on the demo listing page.
Next, we'll modify render()
to show this list. Below our form
element, we'll create a new div
. This new container div
will hold a heading, h3
, and our names list, a ul
parent with a li
child for each name. Here's our updated render()
method:
render() {
return (
<div>
<h1>Sign Up Sheet</h1>
<form onSubmit={this.onFormSubmit}>
<input
placeholder='Name'
ref='name'
/>
<input type='submit' />
</form>
<div>
<h3>Names</h3>
<ul>
{ this.state.names.map((name, i) => <li key={i}>{name}</li>) }
</ul>
</div>
</div>
);
}
ES2015 gives us a compact way to insert li
children. Since this.state.names
is an array, we can take advantage of its map()
method to return a li
child element for each name in the array. Also, by using "arrow" syntax, for our iterator function in map()
, the li
element is returned without us explicitly using return
.
One other thing to note here is that we provide a
key
prop
to theli
element. React will complain when we have children in an array or iterator (like we do here) and they don't have akey
prop
. React wants this information to keep track of the child and make sure that it can be reused between render passes.We won't be removing or reordering the list here, so it is sufficient to identify each child by its index. If we wanted to optimize rendering for a more complex use-case, we could assign an immutable id to each name that was not tied to its value or order in the array. This would allow React to reuse the element even if its position or value was changed.
See React's documentation on Multiple Components and Dynamic Children for more information.