React Interview Prep
State and Props

Controlled vs Uncontrolled Components

Controlled vs Uncontrolled Components

LinkedIn Hook

You build a login form. It works. The interviewer asks: "Is this controlled or uncontrolled?"

You pause. "What's the difference?"

They follow up: "When would you choose one over the other? Why are file inputs always uncontrolled?"

This is one of those questions that separates developers who use React from developers who understand React. Controlled components keep React in charge of every keystroke. Uncontrolled components let the DOM handle it and grab values only when needed.

In this lesson, I break down both patterns — what "controlled" really means, how uncontrolled components use refs, the tradeoffs between them, form handling with controlled inputs, and why <input type="file"> is the one input React refuses to control.

If an interviewer asks you about forms in React and you can't explain this clearly — this lesson fixes that.

Read the full lesson -> [link]

#React #JavaScript #WebDevelopment #InterviewPrep #Frontend #CodingInterview #ReactForms #ControlledComponents #100DaysOfCode


Controlled vs Uncontrolled Components thumbnail


What You'll Learn

  • What controlled components are and how React "owns" the input value
  • What uncontrolled components are and how the DOM "owns" the value via refs
  • How to build forms using controlled inputs with useState
  • When to choose controlled vs uncontrolled (and the tradeoffs)
  • Why file inputs (<input type="file">) are always uncontrolled in React

The Concept — Who Owns the Value?

Analogy: The Thermostat vs The Open Window

Imagine you're controlling the temperature of a room.

Controlled component = Smart thermostat. You set the exact temperature. Every change goes through your control system. You see the current reading at all times and can validate, reject, or transform any adjustment before it takes effect. You are the single source of truth.

Uncontrolled component = Open window. You open the window and let nature decide the temperature. You don't track it in real time. When someone asks, you walk over, check the thermometer, and read the current value. You only look when you need to.

In React terms:

  • Controlled = React state holds the value. Every change flows through setState. The input always reflects what React says.
  • Uncontrolled = The DOM holds the value. React doesn't track every keystroke. You use a ref to read the value when you need it (like on form submit).

Controlled Components — React Owns the Value

A controlled component is an input whose value is driven by React state. You set the value attribute to a state variable and update that state via onChange. React is the single source of truth.

Code Example 1: Controlled Input

import { useState } from "react";

function LoginForm() {
  // React state owns both values
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    // Values are already in state — no need to read from the DOM
    console.log("Submitting:", email, password);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}               // React controls what the input displays
        onChange={(e) => setEmail(e.target.value)}  // Every keystroke updates state
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="submit">Login</button>
    </form>
  );
}

// User types "a" in the email field:
// 1. Browser fires onChange event
// 2. setEmail("a") updates React state
// 3. React re-renders, input displays "a" (from state)
//
// The input NEVER has a value that React doesn't know about.

Key points:

  • value={email} makes this controlled — React dictates what the input shows
  • onChange is required — without it, the input is locked (read-only)
  • You always have the current value in state — useful for validation, formatting, conditional logic

Code Example 2: Real-Time Validation with Controlled Inputs

import { useState } from "react";

function SignupForm() {
  const [username, setUsername] = useState("");
  const [error, setError] = useState("");

  function handleChange(e) {
    const value = e.target.value;

    // Validate on every keystroke — only possible with controlled inputs
    if (value.length > 0 && value.length < 3) {
      setError("Username must be at least 3 characters");
    } else if (/[^a-zA-Z0-9_]/.test(value)) {
      setError("Only letters, numbers, and underscores allowed");
    } else {
      setError("");
    }

    setUsername(value);
  }

  return (
    <div>
      <input
        type="text"
        value={username}
        onChange={handleChange}
        placeholder="Choose a username"
      />
      {error && <p style={{ color: "red" }}>{error}</p>}
      <p>Preview: @{username || "..."}</p>
    </div>
  );
}

// As the user types "ab":
// Render 1: username="" → no error
// Render 2: username="a" → "Username must be at least 3 characters"
// Render 3: username="ab" → "Username must be at least 3 characters"
//
// As the user types "abc":
// Render 4: username="abc" → error clears

This kind of real-time validation, live preview, and input formatting is only practical with controlled components because React has access to the value on every keystroke.

Controlled vs Uncontrolled Components visual 1


Uncontrolled Components — The DOM Owns the Value

An uncontrolled component lets the DOM handle the input value internally. React doesn't track every keystroke. Instead, you use a ref (useRef) to reach into the DOM and read the value when you need it.

Code Example 3: Uncontrolled Input with useRef

import { useRef } from "react";

function SearchForm() {
  // Create a ref — a persistent reference to a DOM element
  const inputRef = useRef(null);

  function handleSubmit(e) {
    e.preventDefault();
    // Read the value directly from the DOM — only when we need it
    const searchTerm = inputRef.current.value;
    console.log("Searching for:", searchTerm);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        ref={inputRef}               // Attach the ref to the DOM element
        defaultValue=""              // Set initial value (NOT "value")
        placeholder="Search..."
      />
      <button type="submit">Search</button>
    </form>
  );
}

// User types "react hooks":
// - React does NOT re-render on each keystroke
// - The DOM input manages its own value internally
// - On submit, we read inputRef.current.value → "react hooks"

Key points:

  • No value prop — use defaultValue to set an initial value without controlling it
  • No onChange needed (unless you want side effects)
  • useRef creates a mutable container ({ current: null }) that persists across renders
  • inputRef.current gives you the actual DOM node — you can call .value, .focus(), etc.
  • No re-renders on input — better performance for large forms where you don't need live tracking

File Inputs Are Always Uncontrolled

This is a common interview question. The <input type="file"> element is always uncontrolled in React because its value is read-only for security reasons. You cannot programmatically set a file input's value — the user must select the file through the browser's file picker.

Code Example 4: Handling File Inputs

import { useRef, useState } from "react";

function FileUpload() {
  const fileInputRef = useRef(null);
  const [fileName, setFileName] = useState("No file selected");

  function handleFileChange(e) {
    // Read the selected file from the DOM event
    const file = e.target.files[0];
    if (file) {
      setFileName(file.name);
      console.log("Selected file:", file.name, file.size, "bytes");
    }
  }

  function handleUpload() {
    const file = fileInputRef.current.files[0];
    if (file) {
      // Upload logic here
      console.log("Uploading:", file.name);
    }
  }

  return (
    <div>
      <input
        type="file"
        ref={fileInputRef}
        onChange={handleFileChange}     // React can LISTEN to changes, but can't SET the value
        // value={someState}            // This would NOT work — file inputs are read-only
      />
      <p>{fileName}</p>
      <button onClick={handleUpload}>Upload</button>
    </div>
  );
}

// Why always uncontrolled?
// - Security: browsers prevent JavaScript from setting file paths
// - You can't do <input type="file" value="C:/secrets.txt" />
// - React can read the file on change, but cannot control the input's value

Controlled vs Uncontrolled Components visual 2


When to Use Which

ScenarioControlledUncontrolled
Real-time validationYesNo
Conditionally disable submit buttonYesNo
Enforce input format (e.g., phone number)YesNo
Dynamically modify input (uppercase, trim)YesNo
Simple form, just need value on submitOverkillYes
Integrating with non-React code (jQuery, etc.)DifficultYes
File inputsNot possibleYes (always)
Performance-critical with many inputsMore re-rendersFewer re-renders

The general rule: Use controlled components by default. They give you full control over the input at every moment. Use uncontrolled components when you don't need to track the value in real time — simple forms, file inputs, or when integrating with third-party DOM libraries.

React's official documentation recommends controlled components for most use cases.


Common Mistakes

Mistake 1: Setting value Without onChange (Locked Input)

// BROKEN — input appears locked, user cannot type
function BrokenInput() {
  const [name, setName] = useState("Alice");
  return <input value={name} />;  // No onChange — React locks it to "Alice"
}

// FIX — always pair value with onChange
function FixedInput() {
  const [name, setName] = useState("Alice");
  return <input value={name} onChange={(e) => setName(e.target.value)} />;
}

// If you truly want a read-only input, use the readOnly attribute:
// <input value={name} readOnly />

When you set value without onChange, React controls the input but never updates the state. The input is frozen. React will also warn you in the console.

Mistake 2: Confusing defaultValue and value

// WRONG — mixing controlled and uncontrolled
function ConfusedInput() {
  const [name, setName] = useState("Alice");
  return (
    <input
      defaultValue="Alice"           // This is for uncontrolled inputs
      value={name}                   // This is for controlled inputs
      onChange={(e) => setName(e.target.value)}
    />
  );
  // React will warn: a component has both value and defaultValue
}

// CONTROLLED — use value + onChange
<input value={name} onChange={(e) => setName(e.target.value)} />

// UNCONTROLLED — use defaultValue + ref
<input defaultValue="Alice" ref={inputRef} />

Mistake 3: Switching Between Controlled and Uncontrolled

// BROKEN — starts as uncontrolled, then becomes controlled
function UnstableInput() {
  const [name, setName] = useState(undefined); // value is undefined = uncontrolled

  return (
    <input
      value={name}                    // First render: value={undefined} → uncontrolled
      onChange={(e) => setName(e.target.value)}  // After typing: value="a" → controlled
    />
  );
}
// React warning: "A component is changing an uncontrolled input to be controlled"

// FIX — always initialize state with a defined value
const [name, setName] = useState("");  // Empty string, not undefined

Interview Questions

Q: What is the difference between a controlled and uncontrolled component?

A controlled component has its value driven by React state. Every change goes through setState, and the input always reflects the state. An uncontrolled component lets the DOM manage the value internally. You access it via a ref when needed. The key difference is who owns the source of truth — React state (controlled) or the DOM (uncontrolled).

Q: Why are file inputs always uncontrolled in React?

For security reasons, browsers do not allow JavaScript to programmatically set the value of a file input. The user must select the file manually through the browser's file picker. Since React cannot set the value attribute, it cannot "control" the input. You must use a ref to access the selected file.

Q: What happens if you set value on an input but don't provide an onChange handler?

The input becomes read-only. React locks the displayed value to whatever the value prop is. The user can type but the input won't visually update because React re-renders it back to the same state value. React also logs a console warning advising you to add onChange or use readOnly.

Q: When would you choose an uncontrolled component over a controlled one?

Uncontrolled components make sense for simple forms where you only need the value on submit, when integrating with non-React libraries that manipulate the DOM directly, for file inputs (which are always uncontrolled), or in performance-sensitive cases where you want to avoid re-renders on every keystroke. For everything else — especially when you need real-time validation, conditional logic, or input formatting — controlled components are the better choice.

Q: What is the difference between value and defaultValue in React?

value makes an input controlled — React manages the displayed value and re-renders on each change. defaultValue makes an input uncontrolled — it sets the initial value and then the DOM manages changes independently. You should never use both on the same input. value is for controlled components (paired with onChange), and defaultValue is for uncontrolled components (paired with ref).


Quick Reference — Cheat Sheet

+-----------------------------------+-------------------------------------------+
| Concept                           | Key Point                                 |
+-----------------------------------+-------------------------------------------+
| Controlled component              | value={state} + onChange={setState}        |
|                                   | React is the source of truth.             |
+-----------------------------------+-------------------------------------------+
| Uncontrolled component            | defaultValue + ref={useRef()}             |
|                                   | DOM is the source of truth.               |
+-----------------------------------+-------------------------------------------+
| value without onChange             | Input is locked (read-only).              |
|                                   | React warns in console.                   |
+-----------------------------------+-------------------------------------------+
| defaultValue vs value             | defaultValue = uncontrolled (initial).    |
|                                   | value = controlled (every render).        |
+-----------------------------------+-------------------------------------------+
| File input (<input type="file">)  | Always uncontrolled. Browser security     |
|                                   | prevents setting value programmatically.  |
+-----------------------------------+-------------------------------------------+
| When to use controlled            | Validation, formatting, conditional       |
|                                   | logic, dynamic UI based on input.         |
+-----------------------------------+-------------------------------------------+
| When to use uncontrolled          | Simple forms, file uploads, non-React     |
|                                   | integrations, performance-sensitive.      |
+-----------------------------------+-------------------------------------------+
| Common bug                        | useState(undefined) → switching from      |
|                                   | uncontrolled to controlled. Use "".       |
+-----------------------------------+-------------------------------------------+

RULE: Controlled = React state owns the value. Always pair value + onChange.
RULE: Uncontrolled = DOM owns the value. Use defaultValue + ref.
RULE: File inputs are ALWAYS uncontrolled — no exceptions.

Previous: Lesson 2.3 — Lifting State Up & Prop Drilling -> Next: Lesson 2.5 — Immutability in State ->


This is Lesson 2.4 of the React Interview Prep Course — 10 chapters, 42 lessons.

On this page