How to Bind an Array to a Dropdown in React
Advertisement
🧩 Scenario
Architecture Walkthrough
Rendering Options with map()
A <select> dropdown requires each choice to be an <option> element. To build these dynamically from an array, map() over the data and return an <option> for each item. Each <option> needs a key prop (for React's reconciliation), a value prop (what gets submitted or read from e.target.value), and display text between the tags.
For an array of strings, the string itself works as both the key and the value. For an array of objects, the object ID is the value (what you track in state) and the name or label is the display text.
Making It Controlled
An uncontrolled select lets the browser manage the selected state, which makes it hard to read programmatically or synchronize with other UI. A controlled select sets value={selectedValue} on the <select> element and updates state with onChange={(e) => setSelectedValue(e.target.value)}. The same state variable can then be used anywhere: passed to an API call, used to filter another list, or shown in a summary.
e.target.value is always a string, even when the array values are numbers. If your IDs are numbers, parse the value: Number(e.target.value) or parseInt(e.target.value, 10).
Placeholder Option
A disabled, empty-value first option serves as a visual prompt ("Select a country"). Set its value to an empty string and mark it disabled to prevent reselection after a real option is chosen. Check if (!selectedValue) to know when nothing has been selected yet.
Key Code Explained
// Array of strings
const ROLES = ['Admin', 'Editor', 'Viewer'] as const;
type Role = (typeof ROLES)[number];
function RoleSelect({ onRoleChange }: { onRoleChange: (role: Role) => void }) {
const [role, setRole] = useState<Role | ''>('');
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value as Role;
setRole(value);
if (value) onRoleChange(value);
};
return (
<select value={role} onChange={handleChange}>
<option value="" disabled>Select a role...</option>
{ROLES.map((r) => (
<option key={r} value={r}>{r}</option>
))}
</select>
);
}
// Array of objects with id and label
interface Country {
code: string; // 'IN', 'US', 'DE'
name: string; // 'India', 'United States', 'Germany'
}
function CountrySelect({
countries,
onSelect,
}: {
countries: Country[];
onSelect: (code: string) => void;
}) {
const [selectedCode, setSelectedCode] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const code = e.target.value;
setSelectedCode(code);
onSelect(code);
};
return (
<div>
<label htmlFor="country-select">Country</label>
<select id="country-select" value={selectedCode} onChange={handleChange}>
<option value="" disabled>Select a country...</option>
{countries.map((country) => (
<option key={country.code} value={country.code}>
{country.name}
</option>
))}
</select>
{selectedCode && (
<p>Selected: {countries.find((c) => c.code === selectedCode)?.name}</p>
)}
</div>
);
}
// Numeric IDs: convert e.target.value from string to number
interface Plan {
id: number;
name: string;
price: number;
}
function PlanSelect({ plans }: { plans: Plan[] }) {
const [selectedPlanId, setSelectedPlanId] = useState<number | null>(null);
const selectedPlan = plans.find((p) => p.id === selectedPlanId);
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
// e.target.value is always a string — parse to number
const id = parseInt(e.target.value, 10);
setSelectedPlanId(id);
};
return (
<div>
<select
value={selectedPlanId ?? ''}
onChange={handleChange}
>
<option value="" disabled>Choose a plan...</option>
{plans.map((plan) => (
<option key={plan.id} value={plan.id}>
{plan.name} - ${plan.price}/mo
</option>
))}
</select>
{selectedPlan && <p>You selected: {selectedPlan.name}</p>}
</div>
);
}
In PlanSelect, value={selectedPlanId ?? ''} handles the null initial state by falling back to an empty string, which matches the placeholder option's value. Without this, the <select> would receive null as its value, which React coerces to the string "null" and matches no option, leaving the browser to display the first option instead of the placeholder.
Tradeoffs
| Pattern | Controlled | Value type | Use when |
|---|---|---|---|
| String array | Yes | string | Simple choices with no separate ID |
| Object array | Yes | string (ID) | Data from API with distinct value and label |
| Numeric ID object | Yes | number (parsed) | API IDs are numbers, not strings |
What Interviewers Actually Check
- Whether you make the select controlled with
valueandonChange - Whether you use a stable object ID as the option
keyandvalue - Whether you add a placeholder option for "no selection yet"
- Whether you know
e.target.valueis always a string and can convert it - Whether you can look up the full object from the selected ID when needed
Follow-Up Questions
- How would you implement a multi-select dropdown where the user can choose multiple values simultaneously?
- How would you use
react-selector a similar library for a searchable dropdown with thousands of options? - How would you build a dependent dropdown where selecting a country loads its states from an API?
- How does
react-hook-form'sControllercomponent handle a controlled<select>with Zod validation? - How would you reset the dropdown to its placeholder state when a parent filter changes?
Common Candidate Mistakes
- Not making the select controlled, leaving the browser to manage selection state and making it hard to read the value programmatically
- Using array index as the option key, which breaks when the options array is filtered or reordered
- Not adding a disabled placeholder option, making it ambiguous whether the user has made a selection
- Not knowing that
e.target.valueis a string and passing it as a number ID to an API call that expects a number - Trying to store the entire selected object in state via
e.target.value, which only gives the string value of the selected option
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you render a controlled select from an array of strings?
- Can you render a controlled select from an array of objects with distinct ID and display label?
- Can you add a disabled placeholder option and detect when no real selection has been made?
- Can you explain why
e.target.valueis always a string and how to convert it to a number? - Can you look up the full object from the selected ID when you need more than the ID?
Summary
Binding an array to a dropdown combines list rendering and the controlled component pattern. Use map() over the array to produce <option> elements with a key (the stable ID), a value (what gets stored in state), and display text. The <select> itself is controlled with value={selectedValue} and onChange updating state.
For arrays of objects, the value on each <option> is the object ID (a string or number serialized to a string), and the display text is the human-readable name. When you need the full object, look it up from the array using find() with the selected ID. e.target.value is always a string; convert it with parseInt or Number() when your IDs are numeric.
Always add a disabled, empty-value placeholder option as the first <option> to prompt the user to make a selection. Without it, the first real option appears selected on initial render even when no selection has been made, and validation that requires a choice becomes ambiguous.
How do I know which option the user selected?
Use a controlled select with value={selectedValue} and onChange={(e) => setSelectedValue(e.target.value)}. The e.target.value string gives you the value of the selected option.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement