How to Restore Text Into React Inputs

Published: October 1, 2026. Updated: October 2026 by Andrew Apell, who builds Form ReDraft

If you have ever built a browser extension, autofill script or test harness that writes into a React form, you have probably hit this: the value appears in the box, and then it vanishes a moment later as soon as anything re-renders.

This is not a React bug. It is React working as designed, and understanding it explains a whole family of extension bugs that users report as "it does not work".

What a controlled input actually is

In a React application, the component that renders an input also stores the value. The rendered value attribute is a reflection of that stored state, not the source of truth.

When you type, React intercepts the keystroke, runs the change handler, updates its state and re-renders the input from that state. The browser element is a display surface.

Now consider what happens when something outside React writes to the element directly:

The element changes, React does not hear about it, React still believes the old value is correct, and the next render puts the old value back.

The window between the write and the next render is why this is so confusing to observe. The text sits there looking correct. Then a keystroke, a timer or a parent re-render happens, and the field snaps back to what React thought it contained all along, which was nothing.

Why the property setter is the problem

React installs its own property descriptor on each input element it manages. That descriptor tracks every write so React can tell whether the value changed in a way it did not cause.

So a direct assignment does travel through React's tracker, which records the new value, and that is exactly the problem. React's tracker records the change as an external write and suppresses its own change handling, because a change it did not initiate should not fire a state update. The assignment updates the screen and leaves state untouched.

The consequence is that no amount of careful assignment will work. You need to bypass the tracked property entirely.

The native setter

The original, untracked set function still exists on the prototype. Reaching past the instance to the prototype gives you the original setter, which writes the value with no bookkeeping attached:

Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set

Use the matching prototype for the element you are writing to: HTMLInputElement.prototype for inputs, HTMLTextAreaElement.prototype for textareas. Getting this wrong is a common cause of a restore that silently does nothing, because the setter you called belonged to a different element type.

The sequence that works

Three steps, in this order:

  1. Write the value through the prototype setter.
  2. Dispatch an input event with bubbles: true.
  3. Dispatch a change event with bubbles: true.

Order matters. The events come after the write so the handler reads the value you intended. The input event comes first because that is what React and Vue listen for. The change event is kept for older code and for plain page scripts that never migrated.

Both events must bubble. A framework usually attaches its listener at a root element rather than on the field itself, so a non-bubbling event is a message nobody receives.

Focus the field afterwards if you want the caret placed in the restored text. That is a usability touch rather than a correctness requirement, but it makes the restore feel finished rather than mechanical.

How to test that you got it right

A restore that only updates the screen will pass a shallow test and fail a real one. The check that catches it:

  1. Type text into a controlled input so the component state holds a known value.
  2. Trigger the restore with different text.
  3. Watch a state readout rendered by the framework, such as a character counter or a word count.
  4. Force a re-render, by typing a character and deleting it, or by waiting for a timer.

If the readout changes and the restored text survives the re-render, the framework adopted the value. If the box fills but the readout does not move, you wrote to the element and nothing more.

Form ReDraft is tested against exactly this case, using a page that reproduces the mechanism without needing React itself: a textarea with its own value property defined on the instance, and a state value that only updates when an input event arrives.

Contenteditable surfaces

Rich text editors do not use a value property, so the setter trick does not apply. There is nothing to bypass: write the text into the element and dispatch a bubbling input event, which is what ProseMirror, Quill, Slate and Draft.js listen for.

Be aware of a limitation. An editor whose document model is authoritative may re-render the surface from its own state and discard the inserted text. This happens with editors that sync to a server or that virtualise their content. Text is inserted and the events fire correctly, but the editor wins the argument.

FAQ

Why does my restored text disappear from a React input?

Because the value was written to the element without React hearing about it. React still holds the old value in its own state and overwrites the element on the next render.

What is the native value setter?

It is the original set function defined on HTMLInputElement.prototype or HTMLTextAreaElement.prototype. Calling it directly writes the value without triggering any framework bookkeeping.

Do I need to dispatch a change event as well?

Dispatch input first, because that is what modern frameworks listen for. Dispatch change afterwards for older code and for plain page scripts.

Does the same trick work for contenteditable editors?

Yes, without the setter. Write the text into the element and dispatch a bubbling input event, which is what editors such as ProseMirror and Quill listen for.

Restore into the framework, not the screen

Form ReDraft writes through the page interface so that React, Vue and Angular keep your restored text instead of overwriting it on the next render.

Read the documentation
Project Slidecut