Skip to content

Controlled Components

A controlled component is an input whose value is driven by React state. Every keystroke updates state, and state flows back to the input's value prop. React becomes the single source of truth for the field.

React Controls the Input Value

In a controlled input, you set value={state} and update state in onChange. The DOM display always reflects React state, making it easy to validate, transform, or reset fields programmatically.

Controlled components are the default recommendation for most React forms because they enable live validation, conditional disabling, and consistent testing.

function EmailField() {
  const [email, setEmail] = useState('');

  return (
    <input
      type="email"
      value={email}
      onChange={(e) => setEmail(e.target.value)}
      placeholder="you@example.com"
    />
  );
}

The input cannot change without setEmail updating state.

Controlled Input Types

// Text
<input value={text} onChange={e => setText(e.target.value)} />

// Checkbox
<input type="checkbox" checked={agreed} onChange={e => setAgreed(e.target.checked)} />

// Select
<select value={country} onChange={e => setCountry(e.target.value)}>
  • Text inputs use value + onChange.
  • Checkboxes use checked, not value.
  • Selects bind value to the selected option's value.
  • Never mix defaultValue with value on the same input.

Controlled vs Uncontrolled

Quick comparison of input control strategies.

Controlled Uncontrolled
Value source React state DOM
Read value From state Ref / FormData
Live validation Easy Harder
Reset field Set state to '' Reset form ref
Typical hook useState useRef

Controlled Forms with Object State

Multiple fields can share one state object. Use computed property names to update individual keys immutably.

const [form, setForm] = useState({ name: '', email: '' });

const update = (field) => (e) =>
  setForm(f => ({ ...f, [field]: e.target.value }));

<input value={form.name} onChange={update('name')} />

Performance with Many Controlled Fields

Each keystroke re-renders the component owning state. For large forms, split into subcomponents, use uncontrolled inputs with a form library, or register fields with React Hook Form to limit re-renders.

Common Mistakes

  • Setting value={undefined} which makes input uncontrolled/unpredictable.
  • Using value on checkbox instead of checked.
  • Mutating state object instead of spreading into new object.
  • Mixing defaultValue and value on same input.

Key Takeaways

  • Controlled inputs bind value/checked to React state.
  • React state is the single source of truth.
  • Ideal for validation, formatting, and conditional UI.
  • Use object state or form libraries for multi-field forms.

Pro Tip

If you see "A component is changing an uncontrolled input to be controlled" warning, initialize state to '' not undefined.