renderpx
Theme: auto

Controlled vs Uncontrolled

Controlled: the parent owns the value (state) and passes value + onChange. Uncontrolled: the DOM holds the value; parent reads via ref when needed (e.g. submit). Choose per component and support both when building reusable inputs.

The problem I keep seeing

Form inputs need a source of truth. If the parent keeps it in state and passes value and onChange, every keystroke triggers a re-render and the parent can validate or transform. If the DOM holds the value and the parent reads it only on submit (via ref), you avoid re-renders but lose live sync. You need to decide which model to use and, for design-system inputs, often support both so the same component works in controlled and uncontrolled usage.

Naive approach

Everything controlled: every input is value={state} and onChange={setState}. Simple and predictable, but every keystroke updates parent state and can cause unnecessary re-renders or complexity when wrapping in reusable components.

tsx
Loading...

First improvement

Uncontrolled: use defaultValue (not value) and attach a ref. Read ref.current.value on submit or when you need it. The parent doesn’t re-render on each keystroke; good for simple forms where you only care about the final values.

tsx
Loading...

Remaining issues

  • Reusable components: A design-system Input may be used controlled (parent has state) or uncontrolled (parent uses ref). Supporting both means: if value is passed, treat as controlled; otherwise use internal state and optional ref.
  • Don’t switch mode: React warns if you switch from controlled to uncontrolled (or vice versa) for the same component instance; pick one per mount.

Production pattern

For reusable inputs: accept both value + onChange (controlled) and defaultValue (uncontrolled). Inside the component, treat “controlled” when value !== undefined and use that; otherwise use internal state. Never switch from one mode to the other for the same instance. Form libraries (React Hook Form) often use uncontrolled inputs with refs and read values on submit, which reduces re-renders.

tsx
Loading...

When I use this

  • Controlled: When you need live validation, formatting (e.g. phone mask), or conditional UI based on the current value.
  • Uncontrolled: Simple forms where you only need values on submit; fewer re-renders and simpler parent state.

Gotchas

  • value vs defaultValue: In uncontrolled mode you must not pass value (or pass undefined); use defaultValue for the initial value.
  • Warning “switching from controlled to uncontrolled”: Usually means you passed value={undefined} or value={null} at some point; use value={value ?? ''} for strings to keep it controlled.

Form Validation → · All patterns