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
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
refto 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 showsonChangeis 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.
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
valueprop — usedefaultValueto set an initial value without controlling it - No
onChangeneeded (unless you want side effects) useRefcreates a mutable container ({ current: null }) that persists across rendersinputRef.currentgives 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
When to Use Which
| Scenario | Controlled | Uncontrolled |
|---|---|---|
| Real-time validation | Yes | No |
| Conditionally disable submit button | Yes | No |
| Enforce input format (e.g., phone number) | Yes | No |
| Dynamically modify input (uppercase, trim) | Yes | No |
| Simple form, just need value on submit | Overkill | Yes |
| Integrating with non-React code (jQuery, etc.) | Difficult | Yes |
| File inputs | Not possible | Yes (always) |
| Performance-critical with many inputs | More re-renders | Fewer 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 arefwhen 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
valueattribute, it cannot "control" the input. You must use arefto 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
valueprop 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 addonChangeor usereadOnly.
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?
valuemakes an input controlled — React manages the displayed value and re-renders on each change.defaultValuemakes an input uncontrolled — it sets the initial value and then the DOM manages changes independently. You should never use both on the same input.valueis for controlled components (paired withonChange), anddefaultValueis for uncontrolled components (paired withref).
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.