principlejavascriptreactModerate
Controlled vs uncontrolled inputs — choose one and be consistent
Viewed 0 times
controlled inputuncontrolled inputdefaultValuereact-hook-formformrefvalue prop
browser
Error Messages
Problem
Mixing controlled and uncontrolled input patterns in the same form causes the 'changing controlled to uncontrolled' warning, inconsistent behavior, and state synchronization issues. Developers also reach for controlled inputs for every form when uncontrolled is simpler.
Solution
Controlled: value + onChange for every change. Uncontrolled: defaultValue + ref for final value.
// CONTROLLED — React owns the value at all times
function ControlledForm() {
const [name, setName] = useState('');
function handleSubmit(e) {
e.preventDefault();
submitData(name); // state is always current
}
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
);
}
// UNCONTROLLED — DOM owns the value, React reads it on submit
function UncontrolledForm() {
const nameRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
submitData(nameRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<input defaultValue="" ref={nameRef} /> {/ DOM owns value /}
<button type="submit">Submit</button>
</form>
);
}
// For complex forms, prefer react-hook-form (uncontrolled by default, validation built-in)
// CONTROLLED — React owns the value at all times
function ControlledForm() {
const [name, setName] = useState('');
function handleSubmit(e) {
e.preventDefault();
submitData(name); // state is always current
}
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
);
}
// UNCONTROLLED — DOM owns the value, React reads it on submit
function UncontrolledForm() {
const nameRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
submitData(nameRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<input defaultValue="" ref={nameRef} /> {/ DOM owns value /}
<button type="submit">Submit</button>
</form>
);
}
// For complex forms, prefer react-hook-form (uncontrolled by default, validation built-in)
Why
Controlled inputs re-render on every keystroke — fine for small forms, but with 50 fields and complex validation, it's costly. Uncontrolled inputs let the DOM own the value and are faster. react-hook-form uses uncontrolled inputs for performance while providing a controlled-like API.
Gotchas
- Never switch between controlled (value={x}) and uncontrolled (no value prop) for the same input during its lifecycle
- defaultValue sets the initial uncontrolled value and is ignored on subsequent renders
- File inputs are always uncontrolled — value is read-only and controlled by the browser
- For real-time validation, controlled is easier; for submit-time validation, uncontrolled is simpler
Code Snippets
react-hook-form (recommended for complex forms)
// react-hook-form: uncontrolled performance, controlled DX
import { useForm } from 'react-hook-form';
function Form() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register('name', { required: true })} />
<button type="submit">Submit</button>
</form>
);
}Context
When building forms in React and choosing between React-managed and DOM-managed input values
Revisions (0)
No revisions yet.