Component Patternshigh

Controlled vs Uncontrolled Components

A controlled component has its value driven by React state — the component renders what state says and updates state on change. An uncontrolled component manages its own internal state, and you read its value via a ref when needed.

Memory anchor

Controlled = puppet on strings (React pulls every string). Uncontrolled = a pet with a GPS collar (does its own thing, you check location when needed).

Expected depth

Controlled: <input value={name} onChange={e => setName(e.target.value)} />. Every keystroke flows through React state. This gives you full control for validation, formatting, and conditional updates. Uncontrolled: <input defaultValue='hello' ref={inputRef} />, read inputRef.current.value on submit. Uncontrolled is simpler for forms that only need values on submit. Mixing controlled and uncontrolled on the same input (switching between value and defaultValue) causes React warnings.

Deep — senior internals

File inputs are always uncontrolled in React because their value is read-only for security. The controlled pattern is a specific instance of React's data flow principle: the component is a pure function of its props/state. React 19 form actions and useFormStatus introduce a hybrid approach where form state is managed by React but submitted declaratively. Third-party libraries like React Hook Form use the uncontrolled approach with refs for performance (fewer re-renders than controlled) while providing a controlled-like API.

🎤Interview-ready answer

Controlled components derive their value from React state, giving full control over validation and formatting. Uncontrolled components manage their own state internally, read via refs. I use controlled for complex forms with real-time validation and uncontrolled for simple forms or when integrating non-React libraries. Libraries like React Hook Form use uncontrolled inputs internally for better performance.

Common trap

Setting value without an onChange handler makes the input read-only (React warns). Either add onChange for controlled, or use defaultValue for uncontrolled.

Related concepts