React Input Not Working - Why You Can't Type Anything
4 min read
“You click into the field. You type. Nothing appears. The input looks frozen - and the reason is that you told React to control it, then never told React what to do when it changes.”
<input value={name} placeholder="Type your name" />
The moment you write value={name}, you have told React: "this input always
shows exactly what name holds. Nothing else."
React takes that seriously. On every keystroke, the browser tries to update
the input's displayed text. React steps in and overwrites it back to
name - because that is what you asked for. If name never changes,
the input snaps back to the same value every single time, instantly.
It is not broken. It is doing exactly what you told it to do, perfectly,
forever.
value={name} is only half of a controlled input. The other half is the
part that updates name when the user types. Without it, name is stuck
at "" forever, and so is the input.
Symptom
The cursor moves. The field accepts focus. Nothing else happens. No error,
no warning in most setups - just a box that silently refuses every
character.
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Type your name"/>
Now the loop closes. User types a character. The browser fires onChange.
setName(e.target.value) updates state to the new text. React re-renders.
value={name} now equals the new text. The input shows it.
Every keystroke runs this full loop. It feels instant because it is - but
it only works because both halves of the contract are there.
This one is not frozen. Typing works fine. Click Reset and the "Saved:"
label correctly flips back to "Alice" - but the input field itself still
shows whatever you last typed.
The Sensei's Hint
defaultValue sets the input's starting value once, at mount, and then
React stops touching it. The input becomes uncontrolled - it manages its
own text from then on, completely independent of state.
That is why Reset half-works. setName("Alice") updates state, the label
re-renders correctly, but the input was never wired to name in the first
place. There is nothing for the new state value to flow into.
Now the input is controlled the same way as the first example. When Reset
calls setName("Alice"), the value prop becomes "Alice" on the next
render, and the input field updates along with the label.
Constraints
Pick one model per input and stay there. value + onChange means React
drives the field - every change must go through state. defaultValue means
the DOM drives the field - React sets it once and never again. Mixing them,
or switching halfway through a component's life, is where both of these
bugs come from.
If typing does nothing at all: you have value without onChange. The
input is fully controlled and frozen on whatever state currently holds.
If typing works but external updates (resets, props, other buttons) don't
reach the field: you have defaultValue where you needed value. The input
is uncontrolled and only the DOM knows what it currently shows.
Both are one-line fixes once you know which contract you actually want.
Two katas, two sides of the same contract. "Frozen Input" is the direct
version - one missing onChange, one completely unresponsive field, cause
and effect are immediate. "Uncontrolled Input Ignores Reset" is the sneakier
one, because typing works and the bug only shows up when something else
tries to change the field. Do the frozen one first to lock in the mental
model, then the reset one to see how it breaks when only half the wiring
is missing.