How to Build a React Drop Down Component: Controlled and Uncontrolled Select Patterns
Implement a React drop down component by rendering a native <select> element with <option> children, managing the selected value through React state for controlled components or using defaultValue for uncontrolled components.
A React drop down component leverages the standard HTML <select> element while integrating with React's state management system. According to the facebook/react source code, React treats <select> like any other DOM element but adds controlled-component semantics that synchronize the selected value with your component state.
Understanding Controlled vs Uncontrolled React Drop Down Components
React supports two fundamental patterns for managing form inputs, including drop down components. The choice between controlled and uncontrolled determines where the source of truth for the selected value resides.
Controlled Components
In a controlled React drop down component, the selected value is stored in React state and updated via the onChange handler. The useState hook, defined in packages/react/src/ReactHooks.js, creates the state primitive that survives re-renders. React writes the value prop to the DOM during every render cycle, ensuring the UI always reflects the current state.
Uncontrolled Components
An uncontrolled React drop down component stores the selected value in the DOM itself. You provide a defaultValue prop to set the initial selection, after which the browser manages subsequent changes. The test suite in packages/react-dom/src/__tests__/ReactDOMSelect-test.js documents this behavior, showing how React writes the default value once during the initial mount and then relinquishes control to the browser.
Building a Controlled React Drop Down Component
The controlled pattern is the recommended approach for most React applications because it enables you to validate, transform, or persist the selected value before it reaches the UI.
import React from 'react';
function Dropdown({ options, initial = '' }) {
const [value, setValue] = React.useState(initial);
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<select value={value} onChange={handleChange}>
{options.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
);
}
export default function App() {
const colors = ['Red', 'Green', 'Blue'];
return (
<div>
<h2>Select a color</h2>
<Dropdown options={colors} initial="Green" />
</div>
);
}
This implementation uses useState from packages/react/src/ReactHooks.js to maintain the selection state. The value prop forces React to keep the DOM synchronized with the state on every render, while onChange captures user interactions through React's synthetic event system.
Creating an Uncontrolled React Drop Down Component
Use the uncontrolled pattern when you need minimal React intervention or are integrating with non-React code.
import React from 'react';
function UncontrolledDropdown({ options, defaultValue = '' }) {
return (
<select defaultValue={defaultValue}>
{options.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
);
}
export function App() {
const fruits = ['Apple', 'Banana', 'Cherry'];
return (
<div>
<h2>Pick a fruit (uncontrolled)</h2>
<UncontrolledDropdown options={fruits} defaultValue="Banana" />
</div>
);
}
As verified in packages/react-dom/src/__tests__/ReactDOMSelect-test.js, React writes the defaultValue to the DOM during the initial mount and then allows the browser to manage subsequent user interactions without React state updates.
Implementing a Multi-Select React Drop Down Component
React supports multi-select drop downs by passing an array to the value prop and using the multiple attribute.
import React from 'react';
function MultiSelect({ options, initial = [] }) {
const [selected, setSelected] = React.useState(initial);
const handleChange = (e) => {
const values = Array.from(e.target.selectedOptions, (opt) => opt.value);
setSelected(values);
};
return (
<select multiple value={selected} onChange={handleChange}>
{options.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
);
}
export function App() {
const planets = ['Mercury', 'Venus', 'Earth', 'Mars'];
return (
<div>
<h2>Choose planets</h2>
<MultiSelect options={planets} initial={['Earth', 'Mars']} />
</div>
);
}
The multiple attribute tells the browser to allow multiple selections. React synchronizes the array of selected values through the value prop, reading selections from event.target.selectedOptions as documented in the synthetic event system tests.
Key Implementation Files in the React Source Code
Understanding the underlying React architecture helps explain why these patterns work. The facebook/react repository contains the authoritative implementations:
-
packages/react/src/ReactHooks.js– DefinesuseStateand other hook primitives that enable stateful React drop down components to persist selection values across renders. -
packages/react-dom/src/__tests__/ReactDOMSelect-test.js– Contains the test suite that specifies expected behavior for<select>elements in both controlled and uncontrolled modes, includingvalueanddefaultValuehandling. -
packages/react-dom/src/events/plugins/__tests__/SelectEventPlugin-test.js– Documents the syntheticselectevent that React dispatches when users interact with drop down components, enabling customonChangehandling.
Summary
- A React drop down component renders a standard
<select>element with<option>children, managed through React's component model. - Controlled components store the selected value in React state using
useState(defined inpackages/react/src/ReactHooks.js) and synchronize via thevalueprop. - Uncontrolled components use
defaultValueto set an initial selection, after which the browser manages the value, as verified inpackages/react-dom/src/__tests__/ReactDOMSelect-test.js. - Multi-select drop downs pass an array to the
valueprop and readevent.target.selectedOptionsto handle multiple selections. - React's synthetic event system dispatches
onChangeevents for drop down interactions, documented in the SelectEventPlugin test suite.
Frequently Asked Questions
What is the difference between controlled and uncontrolled select in React?
A controlled React drop down component uses the value prop and onChange handler to store the selection in React state, giving you programmatic control over the current value. An uncontrolled component uses defaultValue to set the initial selection, after which the browser manages the value internally without React state updates, as implemented in the React DOM reconciler and tested in ReactDOMSelect-test.js.
How do I handle multiple selections in a React drop down?
Add the multiple attribute to your <select> element and pass an array of selected values to the value prop. In your onChange handler, extract the selected options using Array.from(event.target.selectedOptions).map(opt => opt.value) to update your state array. React synchronizes this array with the DOM during each render cycle.
Where does React implement the select element behavior?
React implements <select> element behavior in the React DOM package, specifically within the reconciler logic that handles form elements. The authoritative reference for this behavior is the test file packages/react-dom/src/__tests__/ReactDOMSelect-test.js, which defines how value, defaultValue, and multiple attributes should interact with the DOM. The useState hook used to manage select values is defined in packages/react/src/ReactHooks.js.
Should I use controlled or uncontrolled components for forms?
Use controlled components when you need to validate, transform, or persist form data before it reaches the UI, or when multiple UI elements need to stay synchronized with the same data. Use uncontrolled components when integrating with non-React code, when you need minimal React intervention for simple forms, or when using file inputs that cannot be controlled due to browser security restrictions. For most React applications, controlled components provide more predictable data flow and easier testing.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →