Controlled vs. Uncontrolled Components
Problem
Form inputs behave inconsistently across the application, with some managed by component state and others by the DOM. This creates confusion about where form data lives, makes validation and submission logic unpredictable, and causes bugs when inputs don’t update as expected or lose their values unexpectedly.
Solution
Choose between components that accept values from props (controlled) versus those that manage their own internal state (uncontrolled). Controlled components give you full control at the cost of more code, while uncontrolled components are simpler but offer less control.
Example
This example compares controlled inputs (state-driven) with uncontrolled inputs (DOM-driven) to show the different approaches for managing form data.
// Controlled: Component state drives the input value
// Value is synchronized with state on every change
<input value={value} onChange={(e) => setValue(e.target.value)} />
// Uncontrolled: DOM manages its own state
// Component only sets initial value and reads via ref when needed
<input defaultValue="initial" ref={inputRef} />
Benefits
- Controlled components provide full programmatic control over values.
- Uncontrolled components require less boilerplate for simple forms.
- Clear decision framework for choosing the right approach per use case.
- Controlled pattern enables real-time validation and synchronization.
- Uncontrolled pattern works better with direct DOM manipulation needs.
Tradeoffs
- Mixing controlled and uncontrolled patterns can cause confusion.
- Controlled components require more code and state management.
- Uncontrolled components make testing harder without direct state access.
- Switching between patterns later requires significant refactoring.
- Uncontrolled components lose React’s declarative benefits for forms.