Topic 5.3
Uncontrolled Inputs
In one line
An uncontrolled input keeps its own value in the DOM: you give it a starting value with defaultValue or defaultChecked and read it when you need it, with a ref or with FormData; file inputs are always uncontrolled.
Think of it like this
A paper form at a bank. The clerk gives you the form with a few boxes already filled in, you write on it yourself, and the clerk reads the whole thing only when you hand it back. Nobody watches every letter you write. An uncontrolled input is that paper form: the browser keeps the text, and React reads it at the end.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Uncontrolled input
- A field with no
valueprop. The browser keeps its value; React reads it when needed. - defaultValue
- The starting text of an uncontrolled field. Used once, when the field first appears.
- defaultChecked
- The starting on/off state of an uncontrolled checkbox or radio.
- Ref
- A box from
useRefwhose.currentcan hold a DOM element, so you can read it directly. Phase 8 covers refs. - FormData
- A browser object that collects every named field of a form:
new FormData(form).get("email"). - File input
<input type="file">, which lets the user pick files. Always uncontrolled.- FileList / File
e.target.filesis a FileList; each item is a File with aname,size(bytes) andtype.- key (to reset)
- Changing a component's
keymakes React create it from scratch, so defaults apply again.
Step by step
01Give the field a starting value, not a value
No value, no onChange, no state. The browser owns what the user types.
<input name="q" defaultValue="react forms" />
<input type="checkbox" name="exact" defaultChecked />02Read it with a ref
useRef(null) makes a box. React puts the DOM element in ref.current after the commit. Read .value in an event handler, never during render.
const qRef = useRef(null);
function handleSearch() {
console.log("searching for", qRef.current.value);
}
<input ref={qRef} defaultValue="react forms" />
<button type="button" onClick={handleSearch}>Search</button>03Or read the whole form with FormData
Give every field a name. On submit, new FormData(form) collects them all. No refs needed.
function handleSubmit(e) {
e.preventDefault();
const data = new FormData(e.currentTarget);
console.log(data.get("q"), data.get("exact")); // "react forms" "on"
}04Reset defaults with a key
When the starting data changes, a new key gives a fresh field that reads the new defaultValue.
<form key={user.id}>
<input name="name" defaultValue={user.name} />
</form>05Decide per form
Ask one question: does the screen need to change while the user is typing?
Try it yourself
- 1
Count the renders
In the first example, type a few letters in the email box. Predict: how many times does
Newsletter renderedappear? (Still once: no state changes while typing.) - 2
Use defaultValue on a select
Add
<select name="freq" defaultValue="monthly">with optionsweeklyandmonthlyto the newsletter, and logdata.get("freq")on submit. - 3
Reset the whole form with one key
In the second example, wrap both inputs in
<form key={user}>and remove the key from the input. Both fields now reset when you click Load Ravi.
Code & diagrams
One render only: typing doesn't re-render an uncontrolled form. An unticked checkbox is missing from FormData, so `get` gives `null`. Change the email and press Subscribe to see the new value.
Preview
Console
Nothing logged yet.Expected console output
Newsletter rendered
ref reads: asha@example.com
FormData reads: asha@example.com on nullThe state changed to Ravi, but the first field still says Asha: `defaultValue` only mattered when the field was created. The second field got a new `key`, so React made a new input that started from the new default.
Preview
Console
Nothing logged yet.Expected console output
plain: Asha
keyed: RaviThe input has no `value` prop: React only listens to it. The list on screen comes from state copied out of `e.target.files`. Pick your own files to see them listed.
Preview
Console
Nothing logged yet.Expected console output
picked: notes.txt 5 bytesBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Start a controlled value as undefined
Write a form with const [form, setForm] = useState({}) and <input value={form.name} onChange={(e) => setForm({ name: e.target.value })} />, then type one letter.
Break #2
Try to control a file input
In the upload example, add value="C:\\notes.txt" to the file input.
Myth vs fact
Myth
Uncontrolled inputs are the old, wrong way.
Fact
They are a normal choice. React's own docs use them for simple forms, React 19 form actions read them through FormData, and libraries like React Hook Form are built on them for speed.
Myth
Changing defaultValue updates the field.
Fact
It is read only when the field is created. Use a new key to start the field over, or make it controlled.
Myth
You can pre-fill a file input.
Fact
No page can choose a file for the user. A file input's value can only be cleared.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
React decides controlled or uncontrolled by whether
value(orchecked) is!= nullon each render. That's whynullandundefinedboth mean "uncontrolled" and why switching betweenundefinedand a string warns. - ▸
FormData gives you only what a real form submission would send: fields need a
name, disabled fields are skipped, unticked checkboxes are missing, and a file input contributesFileobjects. That makes it a good mirror of what the server will receive. - ▸
To show a preview of a chosen image, create a URL with
URL.createObjectURL(file)and callURL.revokeObjectURL(url)when you're done (an effect cleanup, Topic 7.3), or the browser keeps the file in memory. - ▸
Uncontrolled fields keep their DOM state across re-renders but lose it when unmounted. If a tabbed form unmounts hidden tabs, uncontrolled text is lost; React 19.2's
<Activity>(Phase 16) or lifting the values into state keeps it.
Remember this
- 1
An uncontrolled input is a field without a
valueprop. The DOM element stores what the user types, exactly like plain HTML. React doesn't re-render on each keystroke, because no state changes. To give it a starting value, usedefaultValue(text, textarea, select) ordefaultChecked(checkbox, radio). - 2
defaultValueis used only once, when the field first appears. Changing it later does nothing to the text in the field, because the user owns it from then on. To load new starting values (say, a different user's profile), give the field or the form a newkey(Topic 3.3): React then throws the old field away and creates a new one with the new default. - 3
There are two ways to read an uncontrolled field. A ref (
useRef, covered fully in Phase 8) is a box that holds the DOM element:<input ref={emailRef} />, thenemailRef.current.value. Or read the whole form with **FormData**:new FormData(form).get("email")returns the value of the field whosenameisemail. FormData needsnameattributes and is usually the simpler choice for whole forms (Topic 5.4). - 4
A file input (
<input type="file">) is always uncontrolled. For security, a page can never choose a file on the user's computer, so itsvaluecan only be set to an empty string. Read the chosen files frome.target.files(aFileListofFileobjects withname,sizeandtype), store what you need in state, and clear the input with a ref or a newkey. - 5
Choosing: use controlled when the UI must react to each change (live validation, a preview, dependent fields, formatting as you type, disabling Submit until valid). Use uncontrolled when you only need the values at the end (a simple contact form, a search submitted with Enter), for file inputs, or when wrapping non-React widgets. Both are normal React; React 19 form actions (Topic 5.4) lean on uncontrolled fields.
- 6
A field must stay one or the other for its whole life. If its
valuestarts asundefined(uncontrolled) and later becomes a string (controlled), React warnsA component is changing an uncontrolled input to be controlled.The usual cause is state that starts asundefinedor an object without that key, likeuseState({})withvalue={form.name}. Always start controlled values as""(orfalseforchecked). Giving a field bothvalueanddefaultValuealso warns.
Explain it without notes
What is an uncontrolled input, and how do you set its starting value?
Compare reading an uncontrolled field with a ref and with FormData.
Why is a file input always uncontrolled, and how do you work with it in React?
What causes the "changing an uncontrolled input to be controlled" warning and how do you fix it?
When would you choose controlled over uncontrolled, and the other way round?
Practice
Build an uncontrolled search form with defaultValue="pizza" that logs the query on submit using FormData.
Build an uncontrolled Remember me checkbox that starts ticked and logs true or false when a button is clicked, using a ref.
Trade-offs
- ↔
Uncontrolled fields mean less code and no render per keystroke, but the UI can't respond to the value until you read it, and setting a value from code needs a ref or a key reset.
- ↔
Controlled fields give full control and instant feedback, at the cost of state for every field and a render on every change.
Done when you can
I can write uncontrolled fields with
defaultValueanddefaultChecked.I can read field values with a ref and with FormData.
I can handle a file input and clear it.
I can reset defaults with a
key.I can explain and fix the uncontrolled-to-controlled warning.
I can choose controlled or uncontrolled for a given form.