Forms in React
Controlled inputs, form validation, and handling submission - the patterns for building forms that feel native and stay in sync with state.
What it is, in plain English: In React, form inputs are usually 'controlled' -- their value comes from state, and every keystroke updates that state via onChange. This keeps the displayed value and the underlying data always in sync, and makes validation, formatting, and conditional logic straightforward. The alternative ('uncontrolled' inputs using refs) exists for specific cases but controlled inputs are the default approach.
Keeping form inputs in sync with state
The controlled input pattern
function NameInput() {
const [name, setName] = React.useState("");
return (
<input
value={name} {/* the input's value IS the state */}
onChange={(e) => setName(e.target.value)} {/* every keystroke updates state */}
/>
);
}
// This is "controlled" because React state controls the input's value.
// At every moment, the displayed text and the state value are identical.Why controlled inputs?
// Controlled inputs let you:
// 1. Validate as the user types
// 2. Format input (e.g. force uppercase, add commas to numbers)
// 3. Conditionally enable/disable the submit button
// 4. Clear the form programmatically
function PhoneInput() {
const [phone, setPhone] = React.useState("");
function handleChange(e) {
const digitsOnly = e.target.value.replace(/\D/g, ""); // strip non-digits
setPhone(digitsOnly);
}
return <input value={phone} onChange={handleChange} placeholder="Numbers only" />;
}Different input types
function PreferencesForm() {
const [agree, setAgree] = React.useState(false);
const [plan, setPlan] = React.useState("free");
const [bio, setBio] = React.useState("");
return (
<form>
{/* Checkbox uses 'checked', not 'value': */}
<input type="checkbox" checked={agree} onChange={(e) => setAgree(e.target.checked)} />
{/* Select works like text input -- value + onChange: */}
<select value={plan} onChange={(e) => setPlan(e.target.value)}>
<option value="free">Free</option>
<option value="pro">Pro</option>
</select>
{/* Textarea also uses value + onChange (not children, unlike HTML): */}
<textarea value={bio} onChange={(e) => setBio(e.target.value)} />
</form>
);
}Handling submission
function LoginForm() {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
function handleSubmit(e) {
e.preventDefault(); // stop the page from reloading
console.log("Logging in with:", email, password);
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<button type="submit">Log In</button>
</form>
);
}
Try It Yourself (JSX)
Official Sources
- React Documentation — Reacting to Input with State
- React Documentation — Sharing State Between Components
- React Documentation — Manipulating the DOM with Refs
- React Hook Form Documentation
The Codex links only to official documentation. No third-party tutorials.