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