Skip to content

Controlled vs Uncontrolled Components Interview Questions & Answers

14 questions Updated 2026-06-24 Share:

React controlled vs uncontrolled components interview questions — value vs defaultValue, refs, form handling, when to use each approach.

Read the in-depth guideControlled vs Uncontrolled Components in React — A Complete Guide(opens in new tab)
14 of 14

A controlled component is one where React state is the single source of truth for the input's value. The displayed value comes from state; every change must go through state via an onChange handler.

function ControlledInput() {
  const [value, setValue] = useState('')

  return (
    <input
      value={value}                         // state drives the DOM
      onChange={e => setValue(e.target.value)} // DOM change updates state
    />
  )
}

React keeps the DOM perfectly in sync with state — you can read, validate, or transform every keystroke before it appears.

Rule of thumb: If you need to validate, transform, or react to every change as the user types, use a controlled component.

An uncontrolled component lets the DOM manage its own state. React does not drive the input's value — you only read it when you need it (typically on submit) using a ref.

function UncontrolledForm() {
  const inputRef = useRef(null)

  function handleSubmit(e) {
    e.preventDefault()
    console.log(inputRef.current.value)   // read on demand
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="initial" />
      <button type="submit">Submit</button>
    </form>
  )
}

Because there is no React state involved, uncontrolled inputs are simpler to set up but offer less control over intermediate values.

Rule of thumb: Use uncontrolled components for simple forms where you only care about the final submitted value, not intermediate input.

Prop Type Behaviour
value Controlled React always overrides the DOM value; component is controlled
defaultValue Uncontrolled Sets the initial value once; DOM manages value after that
// Controlled — React owns the value at all times
<input value={stateValue} onChange={e => setState(e.target.value)} />

// Uncontrolled — React only sets the initial value
<input defaultValue="hello" ref={inputRef} />

Mixing value without onChange makes the input read-only from the user's perspective (React prevents edits). React will warn you.

Rule of thumb: Use value when React should own the value; use defaultValue when the DOM should own it after mount.

Prefer controlled components when you need:

  • Instant validation (highlight errors as the user types)
  • Conditional UI based on current field value
  • Dynamic constraints (disable a button until all fields are valid)
  • Programmatic value changes (auto-fill, clear on reset)
function EmailField() {
  const [email, setEmail] = useState('')
  const isValid = email.includes('@')

  return (
    <>
      <input
        value={email}
        onChange={e => setEmail(e.target.value)}
        className={isValid ? 'valid' : 'invalid'}
      />
      <button disabled={!isValid}>Subscribe</button>
    </>
  )
}

Rule of thumb: Most production forms use controlled inputs because they provide the feedback loop needed for good UX.

Uncontrolled inputs are simpler when:

  • You only need the value at submit time (e.g. a basic search box)
  • Integrating with non-React libraries that manage their own DOM
  • Handling file inputs (<input type="file"> is always uncontrolled because the browser controls the file path for security)
  • Rapid prototyping where wiring up state would slow you down
// File inputs are always uncontrolled
function FileUpload() {
  const fileRef = useRef(null)

  function handleUpload() {
    const file = fileRef.current.files[0]
    upload(file)
  }

  return (
    <>
      <input type="file" ref={fileRef} />
      <button onClick={handleUpload}>Upload</button>
    </>
  )
}

Rule of thumb: <input type="file"> is always uncontrolled; for everything else, default to controlled unless simplicity wins.

React logs:

"You provided a value prop to a form field without an onChange handler. This will render a read-only field."

The input is effectively frozen — the user types but the DOM value is always reset to value by React on every render.

// ❌ Read-only — React overrides every keystroke
<input value="fixed" />

// ✅ Controlled with handler
<input value={state} onChange={e => setState(e.target.value)} />

// ✅ Or explicitly read-only with readOnly
<input value="fixed" readOnly />

Rule of thumb: If you pass value, you must also pass onChange (or readOnly if intentionally read-only).

Passing null or undefined makes the input uncontrolled — React treats it the same as omitting value. If you later switch back to a real value, React warns that you're changing a component from uncontrolled to controlled.

// ❌ Starts uncontrolled (value is null initially)
const [text, setText] = useState(null)
<input value={text} onChange={e => setText(e.target.value)} />
// React: "changing an uncontrolled input to be controlled"

// ✅ Always start controlled with an empty string
const [text, setText] = useState('')
<input value={text} onChange={e => setText(e.target.value)} />

Rule of thumb: Initialise controlled state with an empty string ('') rather than null or undefined.

The same value/defaultValue pattern applies to <textarea> and <select>. React normalises them so they behave consistently.

// Controlled textarea
<textarea value={text} onChange={e => setText(e.target.value)} />

// Controlled select
<select value={selected} onChange={e => setSelected(e.target.value)}>
  <option value="a">Option A</option>
  <option value="b">Option B</option>
</select>

// Uncontrolled select with initial selection
<select defaultValue="b" ref={selectRef}>
  <option value="a">Option A</option>
  <option value="b">Option B</option>
</select>

Rule of thumb: Treat <textarea> and <select> exactly like <input> — use value for controlled, defaultValue for uncontrolled.

Technically yes — via the native form submit event's e.target.elements map — but ref is the idiomatic React approach.

// Using ref (recommended)
const inputRef = useRef(null)
<input ref={inputRef} />
// Read: inputRef.current.value

// Using the form submit event (also works, no ref needed)
function handleSubmit(e) {
  e.preventDefault()
  const value = e.target.elements.username.value
}
<form onSubmit={handleSubmit}>
  <input name="username" />
</form>

The name-based approach is fine for very simple forms and mirrors traditional HTML form handling.

Rule of thumb: Use ref for programmatic access during the component's lifetime; use e.target.elements for one-shot submit reading.

Technically yes, but it's an anti-pattern. Mixing paradigms makes the code harder to reason about and breaks the "single source of truth" principle.

// ❌ Confusing mix
function Form() {
  const [email, setEmail] = useState('')   // controlled
  const phoneRef = useRef(null)            // uncontrolled

  function handleSubmit() {
    const data = { email, phone: phoneRef.current.value }
    // collecting values from two different sources
  }
}

// ✅ Consistent — all controlled or all via refs

If you choose controlled, keep all fields controlled. If you use a library like React Hook Form it manages uncontrolled inputs uniformly.

Rule of thumb: Pick one pattern per form and stick to it.

React Hook Form uses uncontrolled inputs by default (via ref), subscribing to change events without storing every keystroke in React state. This results in fewer re-renders — the component only re-renders when validation state changes or on submit.

import { useForm } from 'react-hook-form'

function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm()

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      {/* register wires up ref + onChange internally */}
      <input {...register('email', { required: true })} />
      {errors.email && <span>Email required</span>}
      <button type="submit">Sign up</button>
    </form>
  )
}

For controlled integration use the Controller component or useController hook.

Rule of thumb: React Hook Form is the pragmatic choice for complex forms — it gives controlled-like DX with uncontrolled performance.

Call the state setter with the original default values. Because the input values are driven by React state, setting state is all you need.

const INITIAL = { name: '', email: '' }

function ProfileForm() {
  const [form, setForm] = useState(INITIAL)

  function reset() {
    setForm(INITIAL)    // inputs snap back to empty
  }

  return (
    <form>
      <input value={form.name}  onChange={e => setForm(p => ({ ...p, name: e.target.value }))} />
      <input value={form.email} onChange={e => setForm(p => ({ ...p, email: e.target.value }))} />
      <button type="button" onClick={reset}>Reset</button>
    </form>
  )
}

For uncontrolled forms you need to call inputRef.current.value = '' for each field manually, which is why controlled inputs are easier to reset programmatically.

Rule of thumb: Controlled components make reset trivial — just restore state. Uncontrolled forms require manual DOM manipulation.

Store the list in state as an array. Each field reads from its array slot and writes back using its index or a stable id.

function TagsInput() {
  const [tags, setTags] = useState(['react', 'hooks'])

  function handleChange(index, value) {
    setTags(prev => prev.map((t, i) => i === index ? value : t))
  }

  function addTag() {
    setTags(prev => [...prev, ''])
  }

  function removeTag(index) {
    setTags(prev => prev.filter((_, i) => i !== index))
  }

  return (
    <>
      {tags.map((tag, i) => (
        <div key={i}>
          <input value={tag} onChange={e => handleChange(i, e.target.value)} />
          <button onClick={() => removeTag(i)}>✕</button>
        </div>
      ))}
      <button onClick={addTag}>+ Add</button>
    </>
  )
}

Rule of thumb: Use stable ids rather than array indices as key when items can be reordered to avoid React losing focus state.

Controlled components give React full authority over the input value at the cost of wiring up onChange state; uncontrolled components let the DOM own the value and are simpler to set up but harder to integrate with validation and dynamic UI.

Controlled  → React state is truth → more wiring, more power
Uncontrolled → DOM is truth         → less wiring, less control

The React docs historically recommended controlled components for most cases; today, form libraries (React Hook Form) offer a third path that is uncontrolled under the hood but feels controlled in the API.

Rule of thumb: Default to controlled unless performance or third-party DOM libraries push you toward uncontrolled.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel