Controlled vs Uncontrolled Forms
In this page:
What Makes an Input 'Controlled'
An input is controlled when its value prop is driven by React state, and onChange updates that state. This means React is the single source of truth for what's in the box at any moment — the displayed value always matches state exactly.
Note: Controlled inputs make it trivial to disable the submit button until a field is valid, since you always have the current value in state.
Warning: A controlled input needs BOTH value and onChange — value alone without onChange makes the input impossible to type into.
Example: What Makes an Input 'Controlled'
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [value, setValue] = React.useState("");
return <div><input value={value} onChange={e => setValue(e.target.value)} /><p>You typed: {value}</p></div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
What Makes an Input 'Uncontrolled'
An uncontrolled input manages its own internal value the normal HTML way, with no value prop from React. To read its current value, you attach a ref and access ref.current.value when you actually need it, like on form submit.
Note: Use defaultValue (not value) to set an uncontrolled input's starting content, so React doesn't try to control it.
Warning: Reading ref.current.value on every render instead of only on submit defeats the point of using an uncontrolled input.
Example: What Makes an Input 'Uncontrolled'
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const inputRef = React.useRef(null);
const handleClick = () => alert("Value: " + inputRef.current.value);
return <div><input ref={inputRef} defaultValue="Hello" /><button onClick={handleClick}>Read Value</button></div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Choosing Between the Two Approaches
Controlled inputs are usually the default choice in React because they make validation, formatting, and conditional UI straightforward. Uncontrolled inputs are useful for simple forms, file inputs (which can't be controlled), or when integrating a non-React widget.
Note: File inputs (<input type=file>) are always uncontrolled in React — their value can't be set programmatically for security reasons.
Warning: Don't mix the two styles on the same input across renders (sometimes passing value, sometimes not) — React will log a warning about switching input types.
Example: Choosing Between the Two Approaches
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [controlled, setControlled] = React.useState("");
const uncontrolledRef = React.useRef(null);
return (
<div>
<input value={controlled} onChange={e => setControlled(e.target.value)} placeholder="Controlled" />
<input ref={uncontrolledRef} defaultValue="" placeholder="Uncontrolled" />
<p>Controlled value: {controlled}</p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Switching an input from controlled to uncontrolled (or back) during its lifetime, which React warns about.
- Using a ref to read an uncontrolled input's value on every render instead of only when needed (like on submit).
- Not providing defaultValue for an uncontrolled input, leaving it blank unexpectedly.
- A controlled input's value comes from React state and is updated via onChange.
- An uncontrolled input manages its own value internally; React reads it via a ref only when needed.
- Controlled inputs make validation and conditional logic easier since the value is always available in state.
- Uncontrolled inputs are simpler for very basic forms or integrating with non-React code.
No React-version restriction — refs and controlled inputs work identically across all supported React versions.
Chapter Quiz — Complete all 8 topics to unlock
0/8 topics done
Complete these topics first: