An HTML input normally manages its own value internally. In React, the standard pattern — a controlled input — makes state the single source of truth instead, with the input just displaying whatever state says.
function NameForm() {
const [name, setName] = useState('');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}Every keystroke fires onChange, which updates state, which re-renders the input with the new value — visually instant, but it's genuinely a round trip through state every time, not the browser's own default typing behavior.
value needs onChange to actually work
Setting value without an onChange handler makes the input permanently stuck at that value — typing does nothing, since nothing ever updates the state driving value. This is one of the most common early mistakes with controlled inputs.
function NameForm() {
const [name, setName] = useState('');
function handleSubmit(e) {
e.preventDefault(); // stop the browser's default full-page reload
console.log('Submitted:', name);
}
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
);
}One useState object with all the fields, updated by field name, is the common pattern once a form grows past one or two inputs:
function SignupForm() {
const [formData, setFormData] = useState({ name: '', email: '' });
function handleChange(e) {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value }); // computed property name, from ES6
}
return (
<form>
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
</form>
);
}{ ...formData, [name]: value } is exactly the immutable-update pattern from the previous lesson — a new object, one field replaced, using the name attribute to pick which key to update dynamically.
function Preferences() {
const [subscribed, setSubscribed] = useState(false);
return (
<input
type="checkbox"
checked={subscribed}
onChange={(e) => setSubscribed(e.target.checked)} // checked, not value
/>
);
}