Difficulty: Intermediate
Uncontrolled components are form elements that manage their own state internally through the DOM, rather than through React state. Instead of binding value and onChange, you use a ref to access the DOM element's current value when you need it - typically on form submission. The DOM itself is the source of truth for the input's value, not React state.
To create an uncontrolled component, you use useRef to create a reference and attach it to the input via the ref attribute. You use defaultValue (instead of value) to set the initial value. After that, the input manages its own state internally. When you need the current value (e.g., on form submit), you read it from ref.current.value.
Uncontrolled components have legitimate use cases. They are simpler for basic forms where you only need the value on submission, not on every keystroke. They integrate more easily with non-React code and third-party DOM libraries. They can have minor performance advantages for forms with many fields, since they don't trigger React re-renders on every input change. And file inputs (<input type="file">) are always uncontrolled in React because their value is read-only and can only be set by user interaction.
The key difference from controlled components is when and how you access the value. Controlled: state is always current, updated on every change. Uncontrolled: you pull the value from the DOM when needed. This means you cannot easily validate on every keystroke, transform input in real-time, or conditionally prevent changes with uncontrolled components.
In practice, controlled components are the recommended default for most React applications. Uncontrolled components are reserved for specific scenarios: integrating with non-React libraries, working with file inputs, rapid prototyping, and very simple forms where real-time validation isn't needed.
function UncontrolledForm() {
const nameRef = React.useRef(null);
const emailRef = React.useRef(null);
const handleSubmit = () => {
// Read values from DOM via refs
const name = nameRef.current ? nameRef.current.value : 'John';
const email = emailRef.current ? emailRef.current.value : 'john@test.com';
console.log('Submitted name:', name);
console.log('Submitted email:', email);
};
// Simulate refs being attached to inputs with values
nameRef.current = { value: 'John' };
emailRef.current = { value: 'john@test.com' };
console.log('Form rendered (no re-renders on typing)');
handleSubmit();
return null;
}
UncontrolledForm();
Refs are used to access DOM values only when needed (on submit). The component doesn't re-render as the user types because no state is being updated.
function DefaultValueDemo() {
// Uncontrolled: uses defaultValue
// <input defaultValue="initial" ref={inputRef} />
// The DOM manages the current value; React only sets the initial
// Controlled: uses value
// <input value={state} onChange={handler} />
// React manages the current value through state
const initialValue = 'initial text';
console.log('defaultValue sets initial DOM value:', initialValue);
console.log('After that, DOM manages its own state');
console.log('value prop keeps input synced with React state');
console.log('defaultValue is for uncontrolled, value is for controlled');
return null;
}
DefaultValueDemo();
defaultValue sets the initial value and then hands control to the DOM. value keeps the input perpetually controlled by React state. Using both simultaneously is a mistake.
function FileUpload() {
const fileRef = React.useRef(null);
const handleUpload = () => {
// Simulate file ref
const files = fileRef.current ? fileRef.current.files : [];
console.log('Files selected:', files.length);
};
// Simulate a file input ref
fileRef.current = { files: [{ name: 'document.pdf' }] };
console.log('File inputs are always uncontrolled');
console.log('Their value is read-only in HTML');
handleUpload();
return null;
}
FileUpload();
File inputs cannot be controlled in React because their value is read-only for security reasons. You must use a ref to access the selected files.
uncontrolled components, useRef, defaultValue, DOM as source of truth, form submission