Controlled vs Uncontrolled

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 2 min

Controlled vs Uncontrolled

When building forms in React, you have to choose between two different ways to handle the input data: Controlled Components and Uncontrolled Components.

This is a frequent interview question that tests your understanding of the DOM vs. the Virtual DOM.

1. Uncontrolled Components (The DOM is the source of truth)

In traditional HTML, form elements like <input>, <textarea>, and <select> maintain their own internal state. If a user types “Hello”, the browser’s DOM stores the string “Hello” inside that input element.

An Uncontrolled Component embraces this default HTML behavior. React does not control the value of the input.

To read the data, you use a ref (specifically the useRef hook) to “reach into” the physical DOM and pull the value out, usually only when the form is finally submitted.

// Uncontrolled Component Example
function UncontrolledForm() {
  const inputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    // We reach into the DOM to get the value when the form is submitted
    alert(`A name was submitted: ${inputRef.current.value}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        {/* We do NOT pass a 'value' or 'onChange' prop. The browser handles it. */}
        <input type="text" ref={inputRef} defaultValue="Bob" />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

2. Controlled Components (React is the source of truth)

A Controlled Component overrides the browser’s default behavior.

Instead of letting the DOM store the text, we force the input to display whatever value exists in a React state variable. When the user types, we intercept the keystroke, update the React state, and React then re-renders the input with the new text.

React becomes the “Single Source of Truth.”

// Controlled Component Example
function ControlledForm() {
  // React state holds the truth
  const [name, setName] = useState('Bob');

  const handleSubmit = (e) => {
    e.preventDefault();
    // We don't need a ref, we just read our state variable
    alert(`A name was submitted: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        {/* We explicitly CONTROL the value, and intercept changes */}
        <input 
          type="text" 
          value={name} 
          onChange={(e) => setName(e.target.value)} 
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

Which should you use?

The official React documentation recommends using Controlled Components for almost all forms.

Why Controlled Components are better:

  1. Instant Validation: Because React knows the value after every single keystroke, you can easily disable the submit button or show an error message (“Password must be 8 characters”) in real-time.
  2. Input Formatting: You can easily format data as the user types (e.g., automatically adding dashes to a phone number or converting everything to uppercase).
  3. Testing: They are much easier to test using tools like React Testing Library.

When to use Uncontrolled Components:

  • When integrating React with a non-React legacy library (like a jQuery plugin) that expects to manipulate the DOM directly.
  • The <input type="file"> tag must be uncontrolled because its value is read-only and controlled exclusively by the user’s operating system file picker. You cannot set its value via React state.

Interview Questions

Q: If an interviewer asks you to build a high-performance 1,000-cell spreadsheet grid in React, should the cells be controlled or uncontrolled?
A: Uncontrolled. If 1,000 cells are strictly controlled by a single global state object, typing a single letter in one cell will force React to re-render all 1,000 cells on every keystroke, causing massive lag. Using uncontrolled inputs with refs completely bypasses the render cycle.