Command Palette

Search for a command to run...

Hectal
PHASE 5Intermediate ~22 min· topic 3 of 7

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 value prop. 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 useRef whose .current can 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.files is a FileList; each item is a File with a name, size (bytes) and type.
key (to reset)
Changing a component's key makes 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.

Search.jsxwhole filejsx
<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.

Search.jsxwhole filejsx
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.

Search.jsxwhole filejsx
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.

EditUser.jsxwhole filejsx
<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?

Decide per formdiagram
Rendering diagram…

Try it yourself

  1. 1

    Count the renders

    In the first example, type a few letters in the email box. Predict: how many times does Newsletter rendered appear? (Still once: no state changes while typing.)

  2. 2

    Use defaultValue on a select

    Add <select name="freq" defaultValue="monthly"> with options weekly and monthly to the newsletter, and log data.get("freq") on submit.

  3. 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

Reading uncontrolled fields with a ref and with FormData New tab

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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Newsletter rendered
ref reads: asha@example.com
FormData reads: asha@example.com on null
defaultValue is read once; a key starts over New tab

The 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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

plain: Asha
keyed: Ravi
File inputs are always uncontrolled New tab

The 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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

picked: notes.txt 5 bytes

Break 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.

terminal
$ Console
── what you'll see ──
A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components

Break #2

Try to control a file input

In the upload example, add value="C:\\notes.txt" to the file input.

terminal
$ Console
── what you'll see ──
Uncaught InvalidStateError: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.
An error occurred in the <input> component.
 
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.

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 (or checked) is != null on each render. That's why null and undefined both mean "uncontrolled" and why switching between undefined and 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 contributes File objects. 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 call URL.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. 1

    An uncontrolled input is a field without a value prop. 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, use defaultValue (text, textarea, select) or defaultChecked (checkbox, radio).

  2. 2

    defaultValue is 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 new key (Topic 3.3): React then throws the old field away and creates a new one with the new default.

  3. 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} />, then emailRef.current.value. Or read the whole form with **FormData**: new FormData(form).get("email") returns the value of the field whose name is email. FormData needs name attributes and is usually the simpler choice for whole forms (Topic 5.4).

  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 its value can only be set to an empty string. Read the chosen files from e.target.files (a FileList of File objects with name, size and type), store what you need in state, and clear the input with a ref or a new key.

  5. 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. 6

    A field must stay one or the other for its whole life. If its value starts as undefined (uncontrolled) and later becomes a string (controlled), React warns A component is changing an uncontrolled input to be controlled. The usual cause is state that starts as undefined or an object without that key, like useState({}) with value={form.name}. Always start controlled values as "" (or false for checked). Giving a field both value and defaultValue also warns.

Explain it without notes

01

What is an uncontrolled input, and how do you set its starting value?

02

Compare reading an uncontrolled field with a ref and with FormData.

03

Why is a file input always uncontrolled, and how do you work with it in React?

04

What causes the "changing an uncontrolled input to be controlled" warning and how do you fix it?

05

When would you choose controlled over uncontrolled, and the other way round?

Practice

01

Build an uncontrolled search form with defaultValue="pizza" that logs the query on submit using FormData.

02

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 defaultValue and defaultChecked.

  • 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.