Controlled vs Uncontrolled Components in React

Intermediate10 min interview
Skills tested:
Explaining what makes an input controlled: value prop + onChange handler tied to stateExplaining what makes an input uncontrolled: no value prop, DOM owns the value, ref reads it on submitUnderstanding why value without onChange produces a read-only input with a console warningKnowing that file inputs are always uncontrolledKnowing that useState(undefined) causes the uncontrolled-to-controlled React warning

Advertisement

🧩 Scenario

Controlled vs uncontrolled is a classic React forms question. Beyond the basic definition, interviewers expect you to know the warning causes, the file input exception, and the performance tradeoff that makes React Hook Form prefer uncontrolled inputs.

Architecture Walkthrough

Why Controlled Components Make State the Single Source of Truth

In a controlled component, the React state variable is the authoritative value of the input at all times. The value prop ties the DOM element to state, and onChange fires on every keystroke to keep state in sync with what the user typed. This creates unidirectional data flow: state flows down to the input as value, and user interaction flows up through onChange to update state.

React always knows the current value without reading the DOM. This enables real-time validation, conditional rendering based on input content, formatted inputs that transform what the user types (phone number masks, currency formatting), and the ability to programmatically set or reset the field by updating state. None of these are straightforward with uncontrolled components because the DOM is the source of truth there, not React.

Why Uncontrolled Components Exist and When They Are the Right Choice

Uncontrolled components defer all value management to the DOM and read the value imperatively via ref.current.value when needed. This is not a legacy pattern to be avoided — it is the correct choice in specific scenarios. File inputs are the clearest example: the browser controls the file picker and the resulting FileList, and React cannot set value on a file input for security reasons, making it inherently uncontrolled. Large forms with many fields are another case where uncontrolled inputs can reduce re-render overhead if none of the fields need real-time validation or cross-field dependencies. Libraries like React Hook Form exploit this by defaulting to uncontrolled inputs and only reading values on submit.

The Warning React Gives When You Switch Between Modes

React warns when an input switches from uncontrolled to controlled or vice versa during its lifetime. This happens when value starts as undefined (making React treat the input as uncontrolled) and is later set to a string. The fix is to always initialize state with an empty string: useState('') not useState(undefined). The same problem appears when a controlled input's onChange handler is missing: React sets the value via the value prop, the user types, the DOM updates temporarily, then React re-renders and resets the input to the state value, making the input appear read-only and generating a console warning.


Key Code Explained

import { useState, useRef } from 'react';

// Controlled input: React state is the source of truth
function ControlledInput() {
  const [value, setValue] = useState(''); // '' not undefined — prevents warning

  return (
    <div>
      {/* value prop makes this controlled; onChange keeps state in sync */}
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Controlled"
      />
      {/* State is immediately available for rendering — no DOM read needed */}
      <p>Character count: {value.length}</p>
      <button disabled={value.trim().length === 0}>Submit</button>
    </div>
  );
}


// Uncontrolled input: DOM owns the value, ref reads it on submit
function UncontrolledInput() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    // Read the value from the DOM only when submitting
    const value = inputRef.current?.value ?? '';
    console.log('Submitted:', value);
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* No value prop — DOM owns the value. defaultValue sets initial value only */}
      <input ref={inputRef} defaultValue="" placeholder="Uncontrolled" />
      <button type="submit">Submit</button>
    </form>
  );
}


// File input: always uncontrolled — browser controls the file picker
function FileUploader() {
  const fileRef = useRef<HTMLInputElement>(null);

  const handleUpload = async () => {
    const file = fileRef.current?.files?.[0];
    if (!file) return;

    const formData = new FormData();
    formData.append('file', file);
    await fetch('/api/upload', { method: 'POST', body: formData });
  };

  return (
    <div>
      {/* value is always read-only on file inputs — cannot be controlled */}
      <input type="file" ref={fileRef} accept="image/*" />
      <button onClick={handleUpload}>Upload</button>
    </div>
  );
}


// Mixed form: controlled text fields + uncontrolled file input
function ProfileForm() {
  const [name, setName] = useState('');
  const [bio, setBio] = useState('');
  const avatarRef = useRef<HTMLInputElement>(null);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    const formData = new FormData();
    formData.append('name', name);   // from controlled state
    formData.append('bio', bio);
    if (avatarRef.current?.files?.[0]) {
      formData.append('avatar', avatarRef.current.files[0]); // from ref
    }

    await fetch('/api/profile', { method: 'POST', body: formData });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
      <textarea value={bio} onChange={(e) => setBio(e.target.value)} />
      <input type="file" ref={avatarRef} accept="image/*" />
      <button type="submit">Save profile</button>
    </form>
  );
}

The mixed form pattern shows the correct way to handle a form with both controlled text fields and a file input. The text fields are controlled because they may need real-time validation. The file input is uncontrolled because it must be. Both are handled in the same submit handler.


Tradeoffs

ApproachRe-renders on keystrokeReal-time validationProgrammatic resetFile input support
ControlledYes (per field)YesYes (setState)No (file input is always uncontrolled)
UncontrolledNoOnly on submitNo (DOM owns it)Yes
React Hook Form (uncontrolled by default)Only on submit (or trigger: onChange)ConfigurableYes (reset())Yes

What Interviewers Actually Check

  • Whether you can explain the unidirectional data flow of a controlled input
  • Whether you know useState(undefined) causes the uncontrolled-to-controlled warning
  • Whether you know that a controlled input without onChange produces a read-only field, not an error
  • Whether you know file inputs are always uncontrolled and can explain the browser security reason
  • Whether you know React Hook Form uses uncontrolled inputs by default and why it is faster on large forms

Follow-Up Questions

  1. You have a controlled input for a phone number that should display formatted output (555) 123-4567 as the user types raw digits. How do you implement the formatting without causing the cursor to jump to the end of the input on every keystroke?
  2. A form has a file upload field alongside controlled text inputs. How do you handle submission when one field is necessarily uncontrolled and the others are controlled?
  3. Your controlled form with 20 fields feels sluggish on low-end devices. Profiling shows 20 components re-rendering on every keystroke. What is the architecture change that fixes this without switching to an external library?
  4. A teammate argues that using defaultValue instead of value on all inputs makes the form simpler. Explain the concrete behavioral difference they will encounter when trying to reset the form programmatically after submission.
  5. React Hook Form defaults to uncontrolled inputs but supports a mode: 'onChange' validation option. When would you use this, and what does it say about the tradeoff between performance and real-time UX?

Common Candidate Mistakes

  • Binding value to state without providing onChange — the input looks frozen and React logs a warning
  • Initializing controlled input state with null or undefined — the input starts uncontrolled and React warns when it becomes controlled
  • Using useRef to store a form field value that needs to update other UI — ref changes are invisible to React
  • Thinking file inputs can be controlled by setting a value prop — the browser ignores it for security reasons
  • Not knowing React Hook Form defaults to uncontrolled, which is why it outperforms Formik on large forms

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you implement a controlled input with value and onChange tied to useState?
  • Can you implement an uncontrolled input with useRef and read the value on submit?
  • Can you explain what happens when value is provided without onChange?
  • Can you explain why file inputs must always be uncontrolled?
  • Can you explain what causes the "uncontrolled to controlled" warning and how to prevent it?

Summary

Controlled components have their value driven entirely by React state: the value prop reflects the state, and onChange updates the state on every keystroke. This makes state the single source of truth and enables real-time validation, formatted inputs, and conditional UI. Uncontrolled components let the DOM own the value; React reads it imperatively via a ref only when needed (typically on submit). Uncontrolled components cause fewer re-renders but cannot support real-time features. File inputs must always be uncontrolled because the browser controls the file picker and does not allow setting the value. Initialize controlled inputs with an empty string, not undefined, to avoid the "uncontrolled to controlled" React warning. React Hook Form defaults to uncontrolled inputs and reads values on submit, which is why it significantly outperforms Formik on large forms.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

When are uncontrolled components the right choice?

File inputs must be uncontrolled because the browser controls the file picker and React cannot set their value. Large forms with no real-time validation are a performance case for uncontrolled inputs — React Hook Form defaults to uncontrolled to avoid per-keystroke re-renders across many fields.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement