React Select

A controlled select ties a dropdown's chosen option to component state, giving you the same predictable data flow as text inputs and textareas.

The Basic Controlled Select

A <select> renders a list of <option> children, and in React you control which one is selected by setting the select element's value prop to match an option's value — not by putting a 'selected' attribute on the <option> itself, as you would in plain HTML.

A Simple Controlled Select

import { useState } from 'react';

function PlanPicker() {
  const [plan, setPlan] = useState('basic');

  return (
    <select value={plan} onChange={(e) => setPlan(e.target.value)}>
      <option value="basic">Basic</option>
      <option value="pro">Pro</option>
      <option value="enterprise">Enterprise</option>
    </select>
  );
}

export default PlanPicker;
Note: Adding selected to an <option> in React JSX is silently ignored by React's rendering — it does not throw, but it also does not select the option. The select's value prop is always the single source of truth.

Rendering Options from Data

Hardcoding <option> tags works for small fixed lists, but most real selects are generated from an array, often fetched from an API. Map over the array and give each generated option a stable, unique key.

Generating Options from an Array

import { useState } from 'react';

const countries = [
  { code: 'US', name: 'United States' },
  { code: 'IN', name: 'India' },
  { code: 'DE', name: 'Germany' },
];

function CountrySelect() {
  const [country, setCountry] = useState('US');

  return (
    <select value={country} onChange={(e) => setCountry(e.target.value)}>
      {countries.map((c) => (
        <option key={c.code} value={c.code}>
          {c.name}
        </option>
      ))}
    </select>
  );
}

export default CountrySelect;

Multiple Selection

Adding the multiple attribute turns a select into a multi-choice list. Its value must then be an array of the selected option values rather than a single string, and reading the new selection out of the onChange event requires looping over e.target.selectedOptions (or e.target.options) since there is no single e.target.value to rely on.

A Multi-Select of Tags

import { useState } from 'react';

function TagSelect() {
  const [tags, setTags] = useState(['react']);

  function handleChange(e) {
    const selected = Array.from(
      e.target.selectedOptions,
      (option) => option.value
    );
    setTags(selected);
  }

  return (
    <select multiple value={tags} onChange={handleChange}>
      <option value="react">React</option>
      <option value="vue">Vue</option>
      <option value="svelte">Svelte</option>
      <option value="angular">Angular</option>
    </select>
  );
}

export default TagSelect;
  • Single select: value is a string, onChange reads e.target.value
  • Multi select: value is an array, onChange reads e.target.selectedOptions
  • optgroup can visually group related <option>s without changing the value logic
  • A placeholder option is usually a disabled <option value=""> at the top of the list
AttributeEffect
valueCurrently selected value(s); drives the controlled behavior
multipleAllows more than one option to be chosen at once
disabled (on select)Locks the whole control
disabled (on option)Makes a single choice unselectable
Note: For selects with dozens of options, search, or async-loaded data, most teams reach for a dedicated library like react-select rather than styling the native element — but the controlled value/onChange contract underneath stays conceptually the same.

Exercise: React Select

How do you turn a <select> element into a controlled component in React?