Controlled vs Uncontrolled Components
Answer
In React, form elements can be either controlled (React manages the value) or uncontrolled (DOM manages the value). This fundamental distinction affects how you handle form data.
Key Difference
Controlled Component
function ControlledInput() {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted:', value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={value} {/* React controls value */}
onChange={handleChange} {/* React handles updates */}
/>
<button type="submit">Submit</button>
</form>
);
}
Uncontrolled Component
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted:', inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
defaultValue="" {/* Initial value */}
ref={inputRef} {/* Get value via ref */}
/>
<button type="submit">Submit</button>
</form>
);
}
Comparison
| Feature | Controlled | Uncontrolled |
|---|---|---|
| Data location | React state | DOM element |
| Value access | State variable | Ref or DOM query |
| Validation | On every change | On submit |
| Instant feedback | Easy | Harder |
| Form libraries | Most prefer | Some support |
| Performance | More re-renders | Fewer re-renders |
When to Use Each
// ✅ Controlled: When you need immediate validation
function ValidationExample() {
const [email, setEmail] = useState("");
const isValid = email.includes("@");
return (
<div>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
{!isValid && <span>Invalid email</span>}
</div>
);
}
// ✅ Controlled: Format input as user types
function PhoneInput() {
const [phone, setPhone] = useState("");
const formatPhone = (value) => {
const digits = value.replace(/\D/g, "");
return digits.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");
};
return (
<input
value={formatPhone(phone)}
onChange={(e) => setPhone(e.target.value)}
/>
);
}
// ✅ Uncontrolled: Simple form, value only needed on submit
function SimpleForm() {
const nameRef = useRef();
const emailRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
sendToServer({
name: nameRef.current.value,
email: emailRef.current.value,
});
};
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} defaultValue="" />
<input ref={emailRef} defaultValue="" />
<button type="submit">Submit</button>
</form>
);
}
// ✅ Uncontrolled: File inputs (always uncontrolled)
function FileUpload() {
const fileRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
const file = fileRef.current.files[0];
uploadFile(file);
};
return (
<form onSubmit={handleSubmit}>
<input type="file" ref={fileRef} />
<button type="submit">Upload</button>
</form>
);
}
Mixing Both Approaches
function MixedForm() {
// Controlled: For validation
const [email, setEmail] = useState("");
// Uncontrolled: For file
const fileRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
submitForm({
email,
file: fileRef.current.files[0],
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input type="file" ref={fileRef} />
<button type="submit">Submit</button>
</form>
);
}
Form Libraries
// React Hook Form (uncontrolled by default, more performant)
function ReactHookFormExample() {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} />
<input {...register("lastName")} />
<button type="submit">Submit</button>
</form>
);
}
Key Points
- Controlled: React state is source of truth
- Uncontrolled: DOM is source of truth
- Use controlled for validation, formatting, conditional logic
- Use uncontrolled for simple forms, file inputs
- File inputs are always uncontrolled
- Most form libraries use uncontrolled for performance