What does the "Control Props" pattern do?
The Control Props pattern is one of the most interesting and powerful architectural techniques in React, which lets you make components both "controlled" and "uncontrolled", at the user's choice.
This pattern underlies many libraries (for example, React Select, Downshift, Headless UI, Radix UI, and others), because it gives flexibility in managing a component's state.
Let's break it down step by step.
Definition
The Control Props Pattern is a pattern in which a component can work in two modes:
- Uncontrolled (internal) - manages its own state
- Controlled (external) - accepts state and callbacks via props, handing control outward
In other words:
The component hands the "levers of control" to the consumer, but if the consumer doesn't take them, the component works on its own.
Example: a toggle switch
Without the pattern (internal state only)
function Toggle() {
const [on, setOn] = useState(false);
return (
<button onClick={() => setOn(!on)}>
{on ? "ON" : "OFF"}
</button>
);
}The component works, but it always manages itself. We cannot control the on state from the outside.
With Control Props
function Toggle({ on: controlledOn, onChange }) {
const [internalOn, setInternalOn] = useState(false);
// If the "on" prop is passed, use it; otherwise use internal state
const isControlled = controlledOn !== undefined;
const on = isControlled ? controlledOn : internalOn;
const toggle = () => {
const newState = !on;
if (!isControlled) setInternalOn(newState); // internal control
onChange?.(newState); // notify the outside world
};
return <button onClick={toggle}>{on ? "ON" : "OFF"}</button>;
}Usage
Internally managed (uncontrolled)
<Toggle />The component stores the state itself.
Externally managed (controlled)
function App() {
const [on, setOn] = useState(false);
return (
<Toggle on={on} onChange={setOn} />
);
}Now the parent controls the on state, and Toggle just "signals" the changes.
What happens
| Variant | Who manages the state | Where the value is stored |
|---|---|---|
| Uncontrolled | The component itself | In useState inside it |
| Controlled | The parent | In the parent's useState |
| Both variants | Determined automatically by props | Depends on whether on is present |
Why Control Props is needed
| Problem | How it's solved |
|---|---|
| You want a component that works "out of the box" but can be customized | Lets you control the state from the outside when needed |
| You need a component that can be either "managed" or "autonomous" | The same code supports both modes |
| You want to plug the component into a complex form or state manager | Pass it value and onChange, and it becomes "controlled" |
| You don't want to lose the convenience of simple usage | Without props it still works on its own |
Another example: with Control Props
function Input({ value: controlledValue, onChange, defaultValue = "" }) {
const [internalValue, setInternalValue] = useState(defaultValue);
const isControlled = controlledValue !== undefined;
const value = isControlled ? controlledValue : internalValue;
const handleChange = (e) => {
if (!isControlled) setInternalValue(e.target.value);
onChange?.(e.target.value);
};
return <input value={value} onChange={handleChange} />;
}Usage:
// Uncontrolled
<Input defaultValue="Hello" />
// Controlled
const [name, setName] = useState("");
<Input value={name} onChange={setName} />One component, two modes of operation. That's the essence of the Control Props Pattern.
Important principles
- Decide which props can be "controllable" For example, value, checked, open, selected, expanded, etc.
- Add a callback for notification For example, onChange, onToggle, onSelect.
- Internal state must stay synchronized with external state If the prop is passed, use it. If not, use your own useState.
- Be predictable A component should not suddenly switch behavior between "controlled" and "uncontrolled" during its lifetime.
Control via a "control prop"
<Toggle on={true} /> // fully controlled by the parent
<Toggle /> // managed internally
<Toggle onChange={handler} /> // works in both scenariosWhere this pattern is used
| Library | Component | Control Prop |
|---|---|---|
| React | , | value / onChange |
| React-Select | value / onChange | |
| Downshift | isOpen, selectedItem, inputValue | |
| Radix UI / Headless UI | open / onOpenChange |
Practically every stateful UI component is implemented via the Control Props Pattern.
Summary
| What | Description |
|---|---|
| Idea | Let a component be either "controlled" or "uncontrolled" |
| How it works | If a prop (for example value or on) is passed, control shifts to the parent |
| Goal | Flexibility: the component works both on its own and as part of complex systems |
| Example | |
| Used in | Inputs, Dialogs, Dropdowns, Comboboxes, Checkboxes |
| Analogy | The component says: "You can control me, or you can let me control myself." |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.