React Intermediate: Forms, Hooks and State
Forms and Lifting State
In this lesson you will
- Build a controlled form with several fields
- Validate input and show errors
- Lift state up to share it between components
Introduction
Forms are in nearly every app. React gives you full control over them, by keeping the form's values in state. This course assumes you finished React Basics. The code is shown for reading, and you can run it in any React project.
Controlled inputs with several fields
A controlled input shows a value from state, and updates the state on every change. With several fields, keep them in one object:
React (JSX)
import { useState } from "react";
function SignupForm() {
const [form, setForm] = useState({ name: "", email: "" });
const [errors, setErrors] = useState({});
function handleChange(e) {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
}
function handleSubmit(e) {
e.preventDefault();
const found = {};
if (!form.name.trim()) found.name = "Please enter your name.";
if (!/^\S+@\S+\.\S+$/.test(form.email)) found.email = "Enter a valid email.";
setErrors(found);
if (Object.keys(found).length === 0) {
console.log("Submit", form);
}
}
return (
<form onSubmit={handleSubmit} noValidate>
<label>
Name
<input name="name" value={form.name} onChange={handleChange} />
</label>
{errors.name && <p role="alert">{errors.name}</p>}
<label>
Email
<input name="email" value={form.email} onChange={handleChange} />
</label>
{errors.email && <p role="alert">{errors.email}</p>}
<button type="submit">Sign up</button>
</form>
);
}Things to notice:
- Each input has a
namethat matches a key in the state, so onehandleChangeserves all of them. { ...form, [name]: value }creates a new object. Never change state objects directly.e.preventDefault()stops the browser from reloading the page.- Errors are state too, shown only when present.
Quick quiz
Why do we write setForm({ ...form, [name]: value }) instead of form[name] = value?
Validation advice
- Validate on the client for quick feedback, but always validate again on the server, because users can bypass the browser.
- Use clear, friendly messages that say how to fix the problem.
- Link labels to inputs, as above, so screen readers announce them.
- Use the right
type, such asemailornumber, so phones show helpful keyboards.
Lifting state up
When two components need the same data, move the state to their closest common parent, and pass it down with props. This is called lifting state up.
React (JSX)
function TemperatureApp() {
const [celsius, setCelsius] = useState(20);
return (
<div>
<Input value={celsius} onChange={setCelsius} />
<Display celsius={celsius} />
</div>
);
}
function Input({ value, onChange }) {
return <input type="number" value={value} onChange={(e) => onChange(Number(e.target.value))} />;
}
function Display({ celsius }) {
return <p>{celsius} C is {(celsius * 9) / 5 + 32} F</p>;
}The parent owns the state. Input reports changes upward through a function prop, and Display simply reads. There is one source of truth, so the two parts cannot disagree.
Keep state minimal
- Do not store values that can be calculated from other state. The Fahrenheit figure above is computed while rendering, not stored.
- Keep state as close to where it is used as possible, and lift it only when sharing is needed.
Try it yourself
Add a password field and a "confirm password" field to the form. Show an error if they differ, and disable the submit button until the form is valid.
Summary
- Controlled forms keep values in state, and one
handleChangecan serve many fields using the input'sname. - Create new state objects instead of changing old ones, and validate on both client and server.
- Lift state to the closest common parent when components need to share it, and compute derived values instead of storing them.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading