Forms allow users to interact with our web application. In HTML forms can have some unexpected behaviors for new developers.
There are many pieces of code involved in adding functionality to a form. We don't expect you to have every single piece of it memorized and understood. We do expect you to be able to go back and use this README.md
as a reference, to study from and to work with as you continue building up your react
knowledge.
- Describe how HTML forms work.
- Use buttons inside HTML forms.
- Build forms utilizing accessibility best practices.
- Distinguish between controlled and uncontrolled form elements in React.
In HTML, the <form></form>
element represents a document section containing interactive controls for submitting information.
- Any
button
without a type attribute, or anybutton
orinput
element with the an attribute oftype="submit"
will cause asubmit
event to be fired. - By default, when forms are submitted they will cause the page to reload in HTML.
- In order to prevent the page from reloading, you should listen for the submit event using JavaScript and make sure to call the
preventDefault
method in your event handler function.
Form elements include various types of interactive elements, such as text fields, checkboxes, radio buttons, checkboxes, select elements, etc. There are also a variety of type attributes for the <input/>
element that can change the behavior of the input as well as restrict the type of data that can be entered.
It is particularly important to pay attention to accessibility best practices when creating forms in HTML.
<form>
<!-- Connect every field to a label using the `for` attribute -->
<!-- The `id` of the input uniquely connects the label and input -->
<label for="email">Email:</label>
<input id="email" type="email" />
<label for="password">Password:</label>
<input id="password" type="password" />
</form>
Forms are stateful by nature. They need to remember information that will be submitted to the server for processing. There is a pattern that we use for this in React. You can use this same pattern for all form processing in React!
import React, { useState } from 'react';
function LoginForm() {
const initialState = { username: '', password: '' };
const [formState, setFormState] = useState(initialState);
const handleChange = event => {
setFormState({ ...formState, [event.target.id]: event.target.value });
};
const handleSubmit = event => {
event.preventDefault();
// do something with the data in the component state
console.log(formState);
// clear the form
setFormState(initialState);
};
// Note that we need to use `htmlFor` instead of `for` in JSX
return (
<form onSubmit={handleSubmit}>
<label htmlFor="username">Username:</label>
<input
id="username"
type="text"
onChange={handleChange}
value={formState.username}
/>
<label htmlFor="password">Password:</label>
<input
id="password"
type="password"
onChange={handleChange}
value={formState.password}
/>
<button type="submit">Login</button>
</form>
);
}
export default LoginForm;
We call these form elements "controlled" because we're allowing React to control the value of them. Let's breakdown the pattern:
When working with forms, it's helpful to have all of the values in a single object. Most often, this is how our APIs will want us to send the data to them. We'll learn more about sending data to APIs in the next unit, but using this pattern will make it easy for us to work with them in the future. An object that contains a property for each input inside the form will work well for most use cases. Assume we have a wireframe for a form like this:
We could have an object that represents it like this:
const initialState = {
issueType: '',
subject: '',
message: '',
};
const [formState, setFormState] = useState(initialState);
Following best practices for form accessibility, build a JSX form that renders the corresponding HTML:
//...
return (
<form>
<label htmlFor="issueType">Type of Issue:</label>
<select id="issueType">
<option value="" disabled selected>Select your issue</option>
<option value="outage">Service Outage</option>
<option value="billing">Billing</option>
<option value="cancel">Cancel Service</option>
</select>
<label htmlFor="subject">Subject:</label>
<input type="text" id="subject" />
<label htmlFor="message">Message</label>
<textarea id="message" cols="30" rows="10"></textarea>
<button type="submit">Send</button>
</form>
);
//...
Remember that JSX is JavaScript, so we can't use a reserved word like for
as an attribute in our label elements. Also, note that we have a button with a type of submit
. All buttons that do not have a type
attribute act as a submit for forms by default in HTML, but it's good practice to be specific.
lets give it some style too to keep it a bit cleaner:
.App {
text-align: center;
color:white;
}
#issueType, #subject, #message{
display:block;
margin: 0 auto;
}
By convention, we'll give our form submit handler the name handleSubmit
. We'll then use the event object that the browser passes to it, and prevent the page from being refreshed by using the event's preventDefault
method. When we're done doing what we need to with the data, we can clear the form by resetting it to it's initialState.
// Event Handler: a callback function to
// be run when the event is observed
const handleSubmit = (event) => {
// we always need to stop the browser
// from submitting the form or the page
// will be refreshed.
event.preventDefault();
// do something with the data in the component state
console.log(formState);
// clear the form
setFormState(initialState);
};
// Event Listener: tells the browser
// which event to listen for on which
// element and what to do when the event
// happens
<form onSubmit={handleSubmit}>
Here's where the real magic happens! This is where our form's fields become controlled and automatically update our state.
We'll start with another event handler. This is the handler that will run every time there is a change to one of our form fields. All HTML form elements have a change event. We listen for this event to make sure that we have a generic handler. Every form element also has a value property that remembers the value the user entered in it or chose. Again, by convention only, we'll name this handler handleChange
.
const handleChange = event => {
setFormState({ ...formState, [event.target.id]: event.target.value });
};
Hmmm... That looks interesting. What are the three dots there? That's called the spread syntax and it's awesome! It basically "spreads" the object properties, along with their corresponding values, inside a new object literal { }
. Then we are using [ event.target.id ]
to compute the property name we would like to replace in said object, again--with its corresponding value event.target.value
.
Now, that we have our handleChange event handler, we need to connect all of the form elements to it.
Add an onChange
attribute to each element and reference the handleChange
function as it's value.
Lastly, we just have to connect each element's value
attribute to it's corresponding property in the formState
.
<form onSubmit={handleSubmit}>
<label htmlFor="issueType">Type of Issue:</label>
<select id="issueType" onChange={handleChange} value={formState.issueType}>
<option value="" disabled selected>Select your issue</option>
<option value="outage">Service Outage</option>
<option value="billing">Billing</option>
<option value="cancel">Cancel Service</option>
</select>
<label htmlFor="subject">Subject:</label>
<input
type="text"
id="subject"
onChange={handleChange}
value={formState.subject}
/>
<label htmlFor="message">Message</label>
<textarea
id="message"
cols="30"
rows="10"
onChange={handleChange}
value={formState.message}
></textarea>
<button type="submit">Send</button>
</form>
Done! A totally generic and reusable way to handle forms in React.