ReactHTMLMedium

Controlled vs uncontrolled components

01

The Short Answer

A controlled component has its form value managed by React state — React is the single source of truth. An uncontrolled component lets the DOM manage the value internally — you read it when needed using a ref.

The distinction comes down to: who owns the data? In a controlled component, React owns it. In an uncontrolled component, the browser's DOM owns it.

02

Controlled Components

In a controlled component, the input's displayed value is always driven by React state. Every keystroke triggers a state update via onChange, and the input re-renders with the new value from state.

The data flow looks like this:

  • User types a character in the input
  • onChange event fires with the new value
  • Your handler calls setState with the new value
  • React re-renders the component
  • The input displays the new value from state

In the controlled form below, notice how every input has a value prop tied to state and an onChange handler that updates that state on every keystroke. React is always in sync with what the user sees. This gives you the power to disable the submit button when fields are empty, validate in real time, or transform input as it's typed.

controlled.tsxtypescript
function ControlledForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    // Values are already in state — use them directly
    console.log({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}                              // React controls the value
        onChange={(e) => setEmail(e.target.value)}  // Every change updates state
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit" disabled={!email || !password}>
        Sign In
      </button>
    </form>
  );
}

Because React always knows the current value, you can do things like instant validation, conditional button disabling, character counting, or formatting the input as the user types — all in real time.

03

Uncontrolled Components

In an uncontrolled component, the DOM itself holds the input's current value. You don't track every keystroke in state. Instead, you read the value when you need it (typically on form submission) using a ref.

uncontrolled.tsxtypescript
function UncontrolledForm() {
  const emailRef = useRef<HTMLInputElement>(null);
  const passwordRef = useRef<HTMLInputElement>(null);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    // Read values from DOM only when needed
    const email = emailRef.current?.value;
    const password = passwordRef.current?.value;
    console.log({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        ref={emailRef}              // DOM manages the value
        defaultValue=""             // Optional initial value
      />
      <input
        type="password"
        ref={passwordRef}
      />
      <button type="submit">Sign In</button>
    </form>
  );
}

Notice the key differences: we use defaultValue instead of value, there's no onChange handler updating state on every keystroke, and we read the value from the ref only when we need it.

Important distinction

value makes an input controlled (React owns it). defaultValue makes it uncontrolled (DOM owns it, React just sets the initial value). Using value without onChange creates a read-only input that the user can't type in.

04

Side-by-Side Comparison

AspectControlledUncontrolled
Source of truthReact stateThe DOM
Value accessAlways available in stateRead via ref when needed
Props usedvalue + onChangedefaultValue + ref
Re-rendersEvery keystroke triggers a re-renderNo re-renders on input change
Instant validationEasy — validate in onChange handlerHard — need imperative DOM access
Dynamic behaviorEasy (disable button, show count, format)Difficult
BoilerplateMore — need state + handler for each fieldLess — just a ref
05

When to Use Which

Use controlled when:

  • You need instant validation (password strength, email format)
  • You want to conditionally disable the submit button
  • You need to format input as the user types (phone number, credit card)
  • Multiple inputs depend on each other (start date must be before end date)
  • You need to programmatically set, reset, or clear values

Use uncontrolled when:

  • Simple forms where you only need the value on submit
  • File inputs (<input type="file"> is always uncontrolled)
  • Integrating with non-React libraries that manage their own DOM
  • Performance-critical forms with many fields (avoids re-renders)
06

The Most Common Mistake

The most common bug is accidentally switching between controlled and uncontrolled mid-render. This happens when state starts as undefined (making the input uncontrolled) and then becomes a string on the first keystroke (making it controlled). React detects this mode switch and throws a warning. The fix is simple — always initialize your state with an empty string so the input is controlled from the very first render.

common-mistake.tsxtypescript
// ❌ Bad — starts uncontrolled, becomes controlled
function BuggyInput() {
  const [value, setValue] = useState<string | undefined>(undefined);

  return (
    <input
      value={value}  // undefined → uncontrolled, then string → controlled
      onChange={(e) => setValue(e.target.value)}
    />
  );
  // React warns: "A component is changing an uncontrolled input
  // to be controlled"
}

// ✅ Good — always controlled from the start
function CorrectInput() {
  const [value, setValue] = useState('');  // Empty string, not undefined

  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

Rule of thumb

Initialize string inputs with '' (empty string), not undefined or null. This ensures the input is controlled from the very first render.

07

Why Interviewers Ask This

This question tests your understanding of React's data flow philosophy and how it applies to forms. Interviewers want to see that you know the trade-offs between both approaches, can choose appropriately for different scenarios, and understand the common pitfall of switching between modes.

Quick Revision Cheat Sheet

Controlled: React state drives the value. Use value + onChange.

Uncontrolled: DOM manages the value. Use defaultValue + ref.

Default choice: Controlled — gives you full power over validation and dynamic behavior

File inputs: Always uncontrolled — you can't set a file input's value programmatically

Don't mix: Never switch between controlled and uncontrolled — initialize state as '', not undefined