Introduction
In HTML, form elements like <input>, <textarea>, and <select> naturally keep their own internal state and update it as the user types. In React, you can choose to let the DOM keep managing that state (uncontrolled components) or have React's state be the single source of truth for the input's value (controlled components). This distinction affects how you read values, validate input, and reset forms.
Cricket analogy: Like a scorer who can either trust the stadium's mechanical scoreboard to tally runs on its own (uncontrolled) or personally record every run in an official scorebook as the single source of truth (controlled) — the choice affects how easily you can verify, correct, or reset the score mid-innings.
Syntax
// Controlled: value comes from state, onChange updates state
function ControlledInput() {
const [value, setValue] = useState('');
return <input value={value} onChange={e => setValue(e.target.value)} />;
}
// Uncontrolled: DOM manages the value, accessed via a ref
function UncontrolledInput() {
const inputRef = useRef(null);
function handleSubmit() {
alert(inputRef.current.value);
}
return <input ref={inputRef} defaultValue="" />;
}Explanation
A controlled component's input value is always driven by React state: you pass 'value' as a prop and update that state in an 'onChange' handler, so every keystroke triggers a re-render with the new value flowing back into the input. This gives you full control — you can validate or transform input on every change, conditionally disable submission, or force the value to something else programmatically. An uncontrolled component instead lets the browser DOM hold the current value internally; React only reads that value when needed, typically via a ref (useRef) and inputRef.current.value, or by using 'defaultValue'/'defaultChecked' to set only the initial value without controlling it afterward. Uncontrolled components require less code and integrate easily with non-React DOM libraries or simple forms, but make live validation, conditional formatting, and dynamically resetting fields harder, since React does not know about each keystroke. Mixing the two — supplying both a 'value' prop and leaving out 'onChange' — produces a React warning about a read-only field, and switching an input from uncontrolled (undefined value) to controlled (defined value) or vice versa across renders also triggers a console warning; the value prop's presence should stay consistent across the component's lifetime.
Cricket analogy: Like a coach who watches and reacts to every single ball bowled in real time, adjusting the field after each delivery (controlled, re-render per keystroke), versus a coach who only checks the scorebook once at the innings break (uncontrolled, read via ref) — mixing the two confuses the whole coaching staff about who's actually in charge of decisions.
Example
function SignupForm() {
// Controlled: validate live as the user types
const [email, setEmail] = useState('');
const isValid = email.includes('@');
// Uncontrolled: just read the value on submit, no live validation needed
const nameRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
console.log({ email, name: nameRef.current.value });
}
return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={e => setEmail(e.target.value)}
style={{ borderColor: isValid ? 'green' : 'red' }}
/>
<input ref={nameRef} defaultValue="" placeholder="Name" />
<button type="submit">Sign up</button>
</form>
);
}Output
As the user types into the email field, React re-renders on every keystroke, immediately updating the border color based on live validation because the value lives in state. The name field, being uncontrolled, updates purely in the DOM with no re-renders triggered by typing; its value is only read once, when the form is submitted, via nameRef.current.value. Both approaches produce the same final submitted data, but the controlled email field enables real-time feedback that the uncontrolled name field cannot provide without additional event handlers.
Cricket analogy: Like a coach who watches the bowler's run-up in live slow-motion replay after every single stride, adjusting feedback in real time with a color-coded signal (controlled, the email field), versus simply reviewing the bowling figures once at the end of the spell from the scorebook (uncontrolled, the name field) — both inform the same final assessment, but only the live review catches issues as they happen.
Key Takeaways
- Controlled components store form values in React state via 'value' + 'onChange', making React the single source of truth.
- Uncontrolled components let the DOM manage form values internally, accessed on demand via refs and 'defaultValue'/'defaultChecked'.
- Controlled components enable live validation, conditional UI, and easy programmatic resets; uncontrolled components require less code for simple cases.
- Never mix a fixed 'value' prop without 'onChange' on the same input, and avoid switching an input between controlled and uncontrolled across the component's lifetime.
- Uncontrolled components pair naturally with useRef and are useful for integrating with non-React code or file inputs, which cannot be controlled.
Practice what you learned
1. What defines a controlled component in React?
2. How do you typically read the current value of an uncontrolled input?
3. Which prop is used to set only the initial value of an uncontrolled input without controlling it afterward?
4. What React behavior occurs if a component's input switches from having no 'value' prop to suddenly receiving one across renders?
Was this page helpful?
You May Also Like
Forms in React
Understand controlled and uncontrolled form inputs and how to manage form state in React.
Form Validation in React
Implement client-side validation logic for React forms, including error state and submit-time checks.
The useRef Hook
Understand how useRef creates a persistent, mutable reference that survives renders without causing re-renders.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics