What is a Controlled Input?
A controlled input’s value is driven entirely by React state, rather than the DOM’s own internal state. The input’s value comes from a state variable, and every keystroke updates that state via onChange.
A Controlled Text Input
import { useState } from "react";
function NameInput() {
const [name, setName] = useState("");
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
);
}Why Use Controlled Inputs?
Since the input’s value always lives in state, it’s trivial to validate as the user types, format the value, disable submission until valid, or reset the form — all through normal state updates, with a single source of truth.
A Complete Controlled Form
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
function handleSubmit(event) {
event.preventDefault();
console.log({ email, password });
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
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>
);
}Checkboxes and Select Elements
Checkboxes use the checked attribute instead of value, and controlled selects work the same way as text inputs — their value is driven by state.
Controlled Checkbox and Select
function Preferences() {
const [subscribed, setSubscribed] = useState(false);
const [plan, setPlan] = useState("free");
return (
<>
<label>
<input
type="checkbox"
checked={subscribed}
onChange={(e) => setSubscribed(e.target.checked)}
/>
Subscribe to newsletter
</label>
<select value={plan} onChange={(e) => setPlan(e.target.value)}>
<option value="free">Free</option>
<option value="pro">Pro</option>
</select>
</>
);
}Managing Multiple Fields with One State Object
For forms with several fields, a single state object (instead of one useState call per field) can reduce boilerplate, updating just the changed key on each keystroke.
One State Object for the Whole Form
function SignupForm() {
const [form, setForm] = useState({ name: "", email: "" });
function handleChange(event) {
const { name, value } = event.target;
setForm((prev) => ({ ...prev, [name]: value }));
}
return (
<form>
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
</form>
);
}Uncontrolled Inputs Exist Too
An "uncontrolled" input lets the DOM manage its own value, read only when needed (usually via a ref) — simpler for very basic cases, but less flexible than the controlled approach for validation and dynamic UI.
Best Practice
Default to controlled inputs for anything beyond the simplest form — the ability to validate, format, and react to every keystroke is almost always worth the small amount of extra boilerplate.