What is the difference between controlled and uncontrolled React Components?
By FrontendPro Editorial Team Updated 8/14/2026
Answer
TL;DR
A controlled component drives a form input from React state - you pass value/checked plus an onChange handler, and React state is the single source of truth. An uncontrolled component lets the DOM keep the value; you read it via a ref (or on submit) and seed the initial value with defaultValue/defaultChecked. Controlled inputs are the right default when you need validation, conditional UI, or to derive other state from the value. Uncontrolled inputs are simpler for write-once forms and for <input type="file">, which is always uncontrolled. React 19 also added first-class form support via the form action prop, useFormStatus, and useActionState, which often removes the need for per-field controlled state.
What is the difference between controlled and uncontrolled React components?
Controlled components
A controlled input passes both value (or checked) and onChange to the element. React state holds the truth; every keystroke flows through a setter.
import { useState } from "react";
function ControlledForm() {
const [name, setName] = useState("");
function handleSubmit(event) {
event.preventDefault();
alert("A name was submitted: " + name);
}
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
Uncontrolled components
An uncontrolled input keeps its value in the DOM. Seed the initial value with defaultValue (or defaultChecked for checkboxes/radios), and read the current value through a ref when you need it.
import { useRef } from "react";
function UncontrolledForm() {
const inputRef = useRef(null);
function handleSubmit(event) {
event.preventDefault();
alert("A name was submitted: " + inputRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" defaultValue="" ref={inputRef} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
defaultValue is only consulted on the initial render - changing it later does not update the DOM. Pairing value with no onChange (or vice versa) makes the input read-only or warns in development; pick one mode per field.
<input type="file"> is always uncontrolled
File inputs cannot be controlled - their value is read-only for security reasons (a page must not be able to set the user's chosen file). Always read files via a ref or from the change/submit event, even in an otherwise controlled form.
function FileForm() {
const fileRef = useRef(null);
function handleSubmit(event) {
event.preventDefault();
const file = fileRef.current.files[0];
// upload file...
}
return (
<form onSubmit={handleSubmit}>
<input type="file" ref={fileRef} />
<button type="submit">Upload</button>
</form>
);
}
React 19 form actions
React 19 made <form action={...}> a first-class way to handle submissions without per-field controlled state. The action receives a FormData object, and useFormStatus / useActionState expose pending and result state.
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "Saving..." : "Save"}</button>;
}
async function saveName(prevState, formData) {
const name = formData.get("name");
// ...persist...
return { ok: true, name };
}
function NameForm() {
const [state, formAction] = useActionState(saveName, null);
return (
<form action={formAction}>
<input name="name" defaultValue={state?.name ?? ""} />
<SubmitButton />
</form>
);
}
This pattern uses uncontrolled inputs (defaultValue plus name) and reads them out of FormData in the action - often the simplest choice for plain submit-style forms.
Key differences
State management
- Controlled: React state owns the value; the DOM mirrors it.
- Uncontrolled: The DOM owns the value; React reads it on demand.
Data flow
- Controlled:
state -> value -> input, andonChange -> setState. - Uncontrolled:
defaultValue -> input, thenref.current.value(orFormData) when read.
When to use which
- Controlled: validation as the user types, conditional disabling, formatting, deriving other state, or anything that needs to react to every keystroke.
- Uncontrolled: simple submit-once forms, integration with non-React code, file inputs, and forms built around React 19 actions.
