React

Autofill that breaks the form

Autofill breaks the form when the browser writes into the field without a React event: state stays empty, the field looks full, and submit sends nothing.

Key points

  • The browser fills fields before React listens for events.
  • autoComplete tokens come from the WHATWG HTML specification.
  • The robust fix is to leave browser-filled fields uncontrolled.

Autofill breaks the form when the browser writes into the field without firing a React event: state stays empty, the field looks full, and the submit sends nothing.

Why state falls behind

The browser fills fields before React is ready to listen for events. In a controlled field the visible text comes from the browser, while the state variable is still an empty string: the two copies never talk.


  Email
  
  Continue

The autoComplete tokens come from the WHATWG HTML specification: email, given-name and one-time-code are recognised values, while an invented token is ignored by the browser.

The fix: leave the value in the DOM

function LoginForm() {
  async function onSubmit(event: React.FormEvent) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    await fetch("/api/login", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(Object.fromEntries(data.entries())),
    });
  }

  return (
    
      Email
      
      Password
      
      Continue
    
  );
}

No useState: the field has no second copy to keep in sync, so whatever autofill wrote is the only copy that exists. FormData reads the DOM at submit and does not depend on an event that may never arrive.

The three symptoms

  • Field full, state empty: validation says the field is required even though the text is visible.
  • Password not updated: the manager fills two fields and only one of them reaches submit.
  • Two values: after the first keystroke the browser value and the state value overwrite each other.
  • Reading the field with useEffect on mount: the password manager writes later, the read returns an empty string and the bug looks like a React problem.

Reproducing the case

npx playwright codegen http://localhost:5173

With the recorder you fill the form by hand and reproduce the submit; Playwright, under the Apache-2.0 licence, keeps the steps and replays them, so the fix needs checking on every browser and not just the development one.

The decision rule

Rule: leave to the DOM the fields the browser knows, email, current-password, street-address, one-time-code, and control only the ones you build yourself, such as a slider or a date picker. The signal is where the value comes from: if it can arrive from outside, the DOM is the source; if it is born from a gesture inside the app, state is fine.

The check does not end with a green test: Chrome, Safari and Firefox write into the field at different moments and with different events. A Playwright test that uses fill() does not reproduce autofill, because it emits the same events as a user; you need a profile with saved credentials or a manual screen recording.

React rebuilds the nodes when the form key changes: a counter used as key clears the DOM and with it whatever the manager had written. Keep the key stable, or hand defaultValue the value that must survive a remount. In development StrictMode mounts the component twice and makes the symptom more frequent: it does not create it, it shows it. If the form lives inside a tab that unmounts, the same effect returns in production.

Live records in the catalogue

Three sign-in forms to compare: login-form, sign-in-form and login-01. To browse by category start from login blocks.

The limitation

There is no standard autofill event: input arrives in most browsers, but not in all of them and not always. The robust fix is to leave browser-filled fields uncontrolled, or to read the value from the DOM at submit.

Where to continue

The comparison between the two schemes is in controlled and uncontrolled forms; the field value is in ref, DOM and state; the measured libraries are in React form libraries.

Frequently asked questions

Can I disable autofill?

Partly. autoComplete set to off is ignored by almost every browser, so living with autofill is the right path, not switching it off.

Why does only the password stay empty?

The manager fills fields in order and some browsers fire the event for the first and not the second. Read the value from the DOM at submit.

Does an uncontrolled field fix it?

Yes, because the value lives in the DOM and the submit reads it with FormData. You lose instant validation, which moves to submit time.

Does the problem exist with react-hook-form too?

Less, because the fields are uncontrolled and the value is read at submit. The first render and dependent fields still need work.

How do I test it without filling the form by hand?

With Playwright you reproduce the submit and replay the test after the fix, so the check stays repeatable.