Skip to main content

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

FeatureControlledUncontrolled
Data locationReact stateDOM element
Value accessState variableRef or DOM query
ValidationOn every changeOn submit
Instant feedbackEasyHarder
Form librariesMost preferSome support
PerformanceMore re-rendersFewer 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