React Controlled vs Uncontrolled Components: Two Explanations Quiz
Two explanations of controlled vs uncontrolled inputs with concrete code, plus companions on `defaultValue` and on extending the controlled idea beyond form inputs.
977 views
16
Define a controlled input and an uncontrolled input in React, and explain who owns the source of truth for the value in each case.
Examples
Example 1:
Input: a text field that the parent component reads on every keystroke
Output: controlled (value comes from React state, parent sees every change via onChange)
Explanation: React holds the state; the DOM is a downstream view.Example 2:
Input: a text field that the parent only inspects when the form is submitted
Output: uncontrolled (the DOM owns the value; React reads it via a ref)
Explanation: the input keeps its own state internally; React queries it on demand.import { useRef, useState } from 'react';
// controlled: React state is the source of truth
function ControlledName() {
const [name, setName] = useState('');
return <input value={name} onChange={(e) => setName(e.target.value)} />;
}
// uncontrolled: the DOM is the source of truth
function UncontrolledName() {
const ref = useRef(null);
const submit = () => console.log(ref.current.value);
return (
<>
<input ref={ref} defaultValue="" />
<button onClick={submit}>submit</button>
</>
);
}
Walk through what happens on each keystroke in a controlled input, contrasted with an uncontrolled input. Why do controlled inputs trigger a re-render but uncontrolled ones do not?
Examples
Example 1:
Input: user types one character into a controlled input bound to a useState value
Output sequence: keystroke -> onChange fires -> setState -> React re-renders the component -> input's value prop equals the new state.
Explanation: state change forces a render, and the render writes the new value back to the DOM.Example 2:
Input: user types one character into an uncontrolled input with defaultValue and a ref
Output sequence: keystroke -> the browser updates the input's internal value -> no React state changes -> no re-render.
Explanation: React never observes the keystroke; it only sees a value when the code calls ref.current.value.import { useEffect, useRef, useState } from 'react';
function Controlled() {
const [v, setV] = useState('');
useEffect(() => { console.log('controlled re-rendered with', v); });
return <input value={v} onChange={(e) => setV(e.target.value)} />;
}
function Uncontrolled() {
const ref = useRef(null);
useEffect(() => { console.log('uncontrolled re-rendered'); });
return <input ref={ref} defaultValue="" />;
}
Explain the difference between value and defaultValue on an <input> in React, and predict what happens if you pass both at the same time.
Examples
Example 1:
Input: <input value="hi" />
Output: a controlled input pinned to "hi"; typing has no visible effect because no onChange writes the new value back.
Explanation: when `value` is set without `onChange`, React treats the field as a read-only controlled input.Example 2:
Input: <input defaultValue="hi" />
Output: an uncontrolled input initialized to "hi"; the user can edit it freely and React does not track changes.
Explanation: defaultValue only sets the initial DOM value; the input is otherwise uncontrolled.Example 3:
Input: <input value="hi" defaultValue="bye" />
Output: React warns about mixing controlled and uncontrolled and uses `value`.
Explanation: passing both is a contradiction; React keeps `value` and warns once.// three behaviors at a glance
function PinnedReadOnly() {
return <input value="hi" readOnly />; // controlled + readOnly
}
function InitialOnly() {
return <input defaultValue="hi" />; // uncontrolled with seed value
}
function Contradiction() {
return <input value="hi" defaultValue="bye" />; // logs a React warning
}
Generalize controlled vs uncontrolled beyond <input>: explain what "controlled" means for a custom component such as a <Modal isOpen onClose> or a <Tabs value onChange>.
Examples
Example 1:
Input: <Modal isOpen={open} onClose={() => setOpen(false)} />
Output: a controlled Modal whose open/closed state lives in the parent.
Explanation: parent owns the state; child notifies via onClose; the child has no internal isOpen state.Example 2:
Input: <Modal defaultOpen />
Output: an uncontrolled Modal that manages its own open/closed state internally and only takes an initial value.
Explanation: child owns the state; parent does not see day-to-day changes, only fires-and-forgets the initial config.// controlled custom component
function Tabs({ value, onChange, items }) {
return items.map((it) => (
<button key={it} aria-pressed={value === it} onClick={() => onChange(it)}>
{it}
</button>
));
}
// uncontrolled custom component
import { useState } from 'react';
function TabsUncontrolled({ defaultValue, items }) {
const [value, setValue] = useState(defaultValue);
return items.map((it) => (
<button key={it} aria-pressed={value === it} onClick={() => setValue(it)}>
{it}
</button>
));
}
