Many interactions on the web are with forms. This lesson will cover using three types of React inputs: checkboxes, options, and text.
- Controlled vs. uncontrolled components
- Use React state to control a checkbox
- Use React state to control an options menu
- Use React state to control a single text input
- Use the form element correctly
- Use React state to control multiple text inputs
- Clearing the inputs after submitting the form
HTML form inputs are elements that already have state. When you create a plain HTML input, as you interact with it (type or click), it updates the input's data and the view.
When adding inputs to React, you must give React control of the inputs to ensure the state is being correctly tracked and updated and that there is one source of truth. When an input exists in a React app that React is not explicitly controlling, it is called an "uncontrolled" component.
Web accessibility is the inclusive practice of ensuring there are no barriers that prevent people with physical disabilities from using the web. Forms contain a lot of built-in web accessibility when they are set up correctly.
For the sake of brevity and clarity in learning how to handle forms in React, many best practices for web accessibility are not demonstrated at this time.
When developing a professional-level app, be sure to take the time to, at a minimum, include:
label
elements with thefor
/htmlFor
attribute- Using an
id
with an input that matches the label'sfor
attribute - Wrapping inputs in a form element
- Using the
submit
event to submit form data
See the end of this reading for more information.
Start by adding state to the app:
const [checked, setChecked] = useState(false);
Add a function that will handle the changes:
function handleCheckboxChange() {}
Initially, when you add an input element, you can interact with it. This checkbox can be clicked to check and uncheck the value. When the checkbox is unchecked the value of checked
is equal to false
, when the checkbox is checked th value of checked
is true
<input type="checkbox />
Note: Most inputs
React will need two more properties to give React control: one that adds the checked
key-value pair, which will show the checkbox as checked or unchecked. An event handler onChange
will be called whenever a change on the element occurs.
<input type="checkbox" checked={checked} onChange={handleCheckboxChange} />
You'll notice that after adding these properties, when you check the checkbox, it stays unchecked because React is now controlling the state, and checked is always set to the initial value of false
.
Add state and create a function to handle the checkbox change.
function handleCheckboxChange() {
setChecked(!checked);
}
The checkbox should now work.
Add state to select an option:
const [selectOption, setSelectOption] = useState("");
Add a function to handle the changes:
Set up state and the event handler:
```js
const [selectOption, setSelectOption] = useState("");
function handleSelectChange() {}
Add the select menu:
<select>
<option value=""></option>
<option value="cats">Cats!</option>
<option value="dogs">Dogs!</option>
</select>
Add the event listener/handler to the select
element.
<select onChange={handleSelectChange}>
Use event.target.value
to get the value of the option:
function handleSelectChange(event) {
console.log(event.target.value);
}
Note: What is the value of
event.target.value
? Is it coming from the key-value pair of thevalue
property or the value inside theoptions
tags?
Add a visual:
<h3>You selected {selectOption}</h3>
Update state:
function handleSelectChange(event) {
setSelectOption(event.target.value);
}
A singular text input follows a similar pattern to the other inputs.
const [nickName, setNickName] = useState("");
function handleNickNameChange(event) {
setNickName(event.target.value);
}
// further down
<input type="text" onChange={handleNickNameChange} />
<h3>
{nickName} selected {selectOption}
</h3>
Usually, users fill out forms with multiple fields. The data entered should be 'processed' when the user has completed filling out the fields and has determined the form is ready to be submitted.
function handleSubmit(event) {
console.log("form submitted");
}
<form onSubmit={handleSubmit}>
<input type="submit" />
</form>;
Press the submit button. What happens? If you look closely, you will notice that the page is reloaded and you won't see the console log. This is the normal default behavior of a form. To prevent this behavior, you must call event.preventDefault()
function handleSubmit(event) {
event.preventDefault();
console.log("form submitted");
}
<form onSubmit={handleSubmit}>
<input type="submit" />
</form>;
You should now be able to see the console log because the page is no longer reloading.
## Multiple text inputs
In a previous example, you made a function whose sole responsibility is to handle the value of `nickname`. Imagine a form that has 10+ text inputs. You could write a handler for each input. However, it would also make sense to create a handler that handles all of the text inputs.
Create an object for a user:
```js
const [user, setUser] = useState({
firstName: "",
lastName: "",
zip: "",
email: "",
password: "",
});
Create a function to handle the change
function handleTextChange(event) {
console.log(event.target.value);
}
The inputs will require several properties:
type
the type of input. Thoughtext
,email
,number
, andpassword
are all text inputs,email
,number
andpassword
have extra functionality to assist users in filling out the forms. MDN Input elementvalue
this is the value seen in the input field and it is the value tracked and controlled by React. Notice that the keys match the user object created during initial state initialization.onChange
the event listener/handler that will callhandleTextChange
id
- this will be thekey
name and will match the user object. This value will be used to dynamically set the text in the different text inputs. Additionally,id
is used in conjunction withfor
/htmlFor
for web accessibility . if you are receiving an unexpected warning along the lines ofA component is changing an uncontrolled input to be controlled
, it is likely because thekey
is missing or there is a spelling mismatch between the initial state and theid
value.
<form onSubmit={handleSubmit}>
<input
type="text"
value={user.firstName}
onChange={handleTextChange}
id="firstName"
/>
<input
type="text"
value={user.lastName}
onChange={handleTextChange}
id="lastName"
/>
<input type="number" value={user.zip} onChange={handleTextChange} id="zip" />
<input
type="email"
value={user.email}
onChange={handleTextChange}
id="email"
/>
<input
type="password"
value={user.password}
onChange={handleTextChange}
id="password"
/>
<input type="submit" />
</form>
You will notice that when you've created these inputs, they are unlabeled, and it is unclear which input is for which field.
Before styling, finish setting up the functionality. This will help clarify which form components are used for input functionality and which assist the user experience.
Add the functionality to update the state of the user object:
function handleTextChange(event) {
setUser({
...user,
[event.target.id]: event.target.value,
});
}
user
is an object. In setUser
, user
is being destructured to copy it. After that, [event.target.id]
, which matches the object keys, will be evaluated. Then the value of event.target.value
is set.
To confirm that this function is updating all the values, log the inputs on submit:
function handleSubmit(event) {
event.preventDefault();
console.log(user);
}
To reset the user data, all the values must be returned to their initial state (empty strings in this example). Form resets typically happen after a form has been submitted, so a good place to call this functionality this is within the handleSubmit
function.
function resetForm() {
setUser({
firstName: "",
lastName: "",
zip: "",
email: "",
password: "",
});
}
function handleSubmit(event) {
event.preventDefault();
console.log(user);
resetForm();
}
Add labels (and some style), and notice that the htmlFor
property matches the id
. This is a best practice for web accessibility:
<form onSubmit={handleSubmit}>
<label htmlFor="firstName">First name:</label>
<input
type="text"
value={user.firstName}
onChange={handleTextChange}
id="firstName"
/>
<label htmlFor="lastName">Last name:</label>
<input
type="text"
value={user.lastName}
onChange={handleTextChange}
id="lastName"
/>
<label htmlFor="zip">Zip code:</label>
<input type="number" value={user.zip} onChange={handleTextChange} id="zip" />
<label htmlFor="email">Email:</label>
<input
type="email"
value={user.email}
onChange={handleTextChange}
id="email"
/>
<label htmlFor="password">Password</label>
<input
type="password"
value={user.password}
onChange={handleTextChange}
id="password"
/>
<input type="submit" />
</form>
import { useState } from "react";
function App() {
const [checked, setChecked] = useState(false);
const [selectOption, setSelectOption] = useState("");
const [nickName, setNickName] = useState("");
const [user, setUser] = useState({
firstName: "",
lastName: "",
zp: "",
email: "",
password: "",
});
function handleCheckboxChange() {
setChecked(!checked);
}
function handleNickNameChange(event) {
setNickName(event.target.value);
}
function handleSelectChange(event) {
console.log(event.target.value);
setSelectOption(event.target.value);
}
function handleSubmit(event) {
event.preventDefault();
resetForm();
}
function handleTextChange(event) {
setUser({
...user,
[event.target.id]: event.target.value,
});
}
function resetForm() {
setUser({
firstName: "",
lastName: "",
zip: "",
email: "",
password: "",
});
}
return (
<div>
<header>
<h1>Form examples</h1>
</header>
<main>
<input
type="checkbox"
checked={checked}
onChange={handleCheckboxChange}
/>
<select onChange={handleSelectChange}>
<option value=""></option>
<option value="cats">Cats!</option>
<option value="dogs">Dogs!</option>
</select>
<input type="text" onChange={handleNickNameChange}></input>
<h3>
{nickName} selected {selectOption}
</h3>
<form onSubmit={handleSubmit}>
<label htmlFor="firstName">First name:</label>
<input
type="text"
value={user.firstName}
onChange={handleTextChange}
id="firstName"
/>
<label htmlFor="lastName">Last name:</label>
<input
type="text"
value={user.lastName}
onChange={handleTextChange}
id="lastName"
/>
<label htmlFor="zip">Zip code:</label>
<input
type="number"
value={user.zip}
onChange={handleTextChange}
id="zip"
/>
<label htmlFor="email">Email:</label>
<input
type="email"
value={user.email}
onChange={handleTextChange}
id="email"
/>
<label htmlFor="password">Password</label>
<input
type="password"
value={user.password}
onChange={handleTextChange}
id="password"
/>
<br />
<input type="submit" />
</form>
</main>
</div>
);
}
export default App;
Resources/Readings: