# Controlled and Uncontrolled Components in React: Key Differences and Best Practices

> Understand controlled vs uncontrolled components in React. Learn how state and refs manage form data, discover best practices, and choose the right approach for your React applications.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: deep-dive
- Published: 2026-02-16

---

**Controlled components store form data in React state via the `value` prop and `onChange` handlers, while uncontrolled components let the DOM manage form data using `defaultValue` and refs, making them ideal for simple forms or third-party integrations.**

When building forms in React, developers must choose between two fundamental patterns for managing input values. According to the facebook/react source code, this decision determines whether your component relies on React's rendering pipeline or delegates to the browser's native DOM. Understanding the technical distinctions between controlled and uncontrolled components in React prevents runtime warnings and subtle bugs in production applications.

## Source of Truth: React State vs. DOM

The primary distinction lies in where the current value lives. In [`packages/react-dom/src/__tests__/ReactDOMInput-test.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMInput-test.js), the test suite validates that controlled inputs treat the `value` prop as authoritative, while uncontrolled inputs respect the DOM's internal state.

| Aspect | Controlled Component | Uncontrolled Component |
|--------|----------------------|------------------------|
| **Source of truth** | React state via `value` prop | Browser DOM |
| **Required props** | `value` and `onChange` | `defaultValue` or `defaultChecked` |
| **Value updates** | Re-render with new `value` prop | DOM updates internally; React does not re-render |
| **Reading values** | From state variable | Via `ref.current.value` |

If you provide a `value` prop without an `onChange` handler, React emits a warning: "You provided a `value` prop to a form field without an `onChange` handler..." as implemented in [`packages/react-dom/src/__tests__/ReactDOMInput-test.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMInput-test.js) around line 225.

## Implementation Patterns and Code Examples

### Controlled Component Pattern

Controlled components require the `value` prop to remain synchronized with React state. This pattern appears throughout the React test suite for validating form behavior.

```javascript
function ControlledInput() {
  const [name, setName] = React.useState('');
  
  return (
    <input
      type="text"
      value={name}
      onChange={e => setName(e.target.value)}
    />
  );
}

```

### Uncontrolled Component Pattern

Uncontrolled components use `defaultValue` to set the initial render value, then rely on the DOM. The React source recommends this for simple forms where you only need the value upon submission.

```javascript
function UncontrolledInput() {
  const inputRef = React.useRef(null);
  
  const handleSubmit = () => {
    alert('Value: ' + inputRef.current.value);
  };
  
  return (
    <>
      <input type="text" defaultValue="Initial" ref={inputRef} />
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}

```

### The Anti-Pattern: Switching Between Modes

React explicitly warns against converting a component from controlled to uncontrolled (or vice versa) during its lifecycle. The test file [`packages/react-dom/src/__tests__/ReactDOMInput-test.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMInput-test.js) contains validation at line 1964: "A component is changing a controlled input to be uncontrolled..."

```javascript
// ❌ BAD: This component may switch modes based on props
function BadExample({ initialValue }) {
  const [value, setValue] = React.useState(initialValue);
  
  // If initialValue is undefined, this starts uncontrolled then becomes controlled
  return (
    <input 
      type="text" 
      value={value} 
      onChange={e => setValue(e.target.value)} 
    />
  );
}

```

## Warnings and Edge Cases from the React Source

The React codebase contains specific validation logic for edge cases that developers frequently encounter.

**Null Values**: Passing `null` as a `value` prop triggers a warning from [`packages/react-dom-bindings/src/shared/ReactDOMNullInputValuePropHook.js`](https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/ReactDOMNullInputValuePropHook.js). The message suggests using an empty string for controlled components or `undefined` for uncontrolled ones.

**Conflicting Props**: If you provide both `value` and `defaultValue`, React warns that the element "must be either controlled or uncontrolled" as tested in [`packages/react-dom/src/__tests__/ReactDOMTextarea-test.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js) at line 741.

**Update Batching**: For controlled components within layers (such as portals), React implements special batching logic in [`packages/react-dom/src/events/ReactDOMUpdateBatching.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/events/ReactDOMUpdateBatching.js) at line 29 to ensure state updates propagate correctly without excessive re-renders.

## Performance and Lifecycle Considerations

Controlled components participate fully in React's render cycle. Every keystroke triggers `onChange → setState → render`, allowing for immediate validation and formatting but potentially causing performance issues in large forms without memoization.

Uncontrolled components bypass React's state management for value updates, reducing re-render overhead. This makes them suitable for high-frequency inputs or when integrating with non-React code that expects standard DOM behavior.

## Summary

- **Controlled components** use the `value` prop and `onChange` handlers, making React state the single source of truth for form data.
- **Uncontrolled components** rely on `defaultValue` and refs, letting the DOM manage the current value and reading it only when necessary.
- **Never switch modes** during a component's lifecycle; React emits warnings in [`ReactDOMInput-test.js`](https://github.com/facebook/react/blob/main/ReactDOMInput-test.js) and related test files when components change between controlled and uncontrolled states.
- **Use controlled components** for complex forms requiring validation, formatting, or real-time UI updates.
- **Use uncontrolled components** for simple forms, file inputs, or performance-critical scenarios where minimizing re-renders is essential.

## Frequently Asked Questions

### Can I use both value and defaultValue on the same input?

No. React treats this as an error and warns that the input "must be either controlled or uncontrolled." According to the test suite in [`packages/react-dom/src/__tests__/ReactDOMTextarea-test.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js) at line 741, providing both props creates ambiguity about whether React or the DOM should manage the value, which can lead to unpredictable behavior.

### Why does React warn when I pass null as a value prop?

Passing `null` as a `value` prop triggers a warning from [`packages/react-dom-bindings/src/shared/ReactDOMNullInputValuePropHook.js`](https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/ReactDOMNullInputValuePropHook.js) because React cannot determine whether you intended the component to be controlled (with an empty string) or uncontrolled (with `undefined`). The warning suggests using an empty string `''` for controlled inputs or `undefined` for uncontrolled ones to clarify your intent.

### How do I convert an uncontrolled component to controlled safely?

To convert an uncontrolled component to controlled without triggering the warning found in [`packages/react-dom/src/__tests__/ReactDOMInput-test.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMInput-test.js) at line 1964, you must ensure the component never receives `undefined` or `null` as the `value` prop after initialization. Initialize your state with a non-null value (such as an empty string) and maintain that type consistently throughout the component's lifecycle. Avoid conditionally rendering the `value` prop based on state that might revert to `undefined`.

### Are uncontrolled components faster than controlled components?

Uncontrolled components can offer better performance for high-frequency inputs because they bypass React's state update cycle. While controlled components trigger `onChange → setState → render` on every keystroke, uncontrolled components allow the DOM to handle input events internally, reducing re-render overhead. However, for most applications, the performance difference is negligible unless you are processing hundreds of inputs simultaneously or implementing complex real-time formatting that would require expensive re-renders.