112

The React way to set which option is selected for a select box, is to set a special value prop on the <select> itself, corresponding to the value attribute on the <option> element you desire to be selected. For a multiple select this prop can accept an array instead.

Now because this is a special attribute, I'm wondering what the canonical way is to retrieve the selected options in the same array-of-option-values-structure when the user changes things (so I can pass it through a callback to a parent component etc), since presumably the same value property won't be available on the DOM element.

To use an example, with a text field you would do something like this (JSX):

var TextComponent = React.createClass({
  handleChange: function(e) {
    var newText = e.target.value;
    this.props.someCallbackFromParent(newText);
  },
  render: function() {
    return <input type="text" value={this.props.someText} onChange={this.handleChange} />;
  }
});

What is the equivalent to replace ??? for this multiple select component?

var MultiSelectComponent = React.createClass({
  handleChange: function(e) {
    var newArrayOfSelectedOptionValues = ???;
    this.props.someCallbackFromParent(newArrayOfSelectedOptionValues);
  },
  render: function() {
    return (
      <select multiple={true} value={this.props.arrayOfOptionValues} onChange={this.handleChange}>
        <option value={1}>First option</option>
        <option value={2}>Second option</option>
        <option value={3}>Third option</option>
      </select>
    );
  }
});

12 Answers 12

127

The same way you do anywhere else, since you're working with the real DOM node as the target of the change event:

handleChange: function(e) {
  var options = e.target.options;
  var value = [];
  for (var i = 0, l = options.length; i < l; i++) {
    if (options[i].selected) {
      value.push(options[i].value);
    }
  }
  this.props.someCallback(value);
}
Sign up to request clarification or add additional context in comments.

17 Comments

CoffeeScript version: (option.value for option in e.target.options when option.selected)
ES6 version: [...event.target.options].filter(o => o.selected).map(o => o.value)
You can also destructure the ES6 arrows: [...e.target.options].filter(({selected}) => selected).map(({value}) => value)
RE: ES6 version, while that looks right it doesn't work. event.target.options is an HTMLOptionsCollection, not an Array.
@zrisher Negative, Ghost Rider. HTMLOptionsCollection is not iterable. However, Array.from still works with it: Array.from(event.target.options).filter(o => o.selected).map(o => o.value)
|
86

With Array.from() and e.target.selectedOptions you can perform a controlled select-multiple:

handleChange = (e) => {
  let value = Array.from(e.target.selectedOptions, option => option.value);
  this.setState({values: value});
}

target.selectedOptions return a HTMLCollection

https://codepen.io/papawa/pen/XExeZY

2 Comments

Any ideas on how to combine the selected options from multiple selects into one array? Say after grabbing them all via document.querySelectorAll('select')?
Note that at the time this question was asked, selectedOptions didn't have good compatibility, and is still not supported by IE. This would be the modern way to do it though.
19

Easiest way...

handleChange(evt) {
  this.setState({multiValue: [...evt.target.selectedOptions].map(o => o.value)}); 
}

2 Comments

This is nice, but HTMLCollectionOf<HTMLOptionElement> is not an array... Apparently Array.from works though. stackoverflow.com/a/49684109/29182
selectedOptions is no longer a property of target in Chrome Version 99.0.4844.51 (Official Build) (64-bit)
13

You can actually find the selectedOptions inside the target... no need to iterate over all the options. Let's imagine you want to send the values to an onChange function passed to your component as props: you can use the following function on the onChange of your multiple select.

onSelectChange = (e) => {
    const values = [...e.target.selectedOptions].map(opt => opt.value);
    this.props.onChange(values);
  };

2 Comments

At the time this question was asked, selectedOptions didn't have good compatibility. It's still not supported by Internet Explorer, so not really usable if you want IE support (at least without a polyfill).
selectedOptions Property is not available in Chrome Version 99.0.4844.51 (Official Build) (64-bit)
12

In case you want to use ref you can get selected values like this:

var select = React.findDOMNode(this.refs.selectRef); 
var values = [].filter.call(select.options, function (o) {
      return o.selected;
    }).map(function (o) {
      return o.value;
    });

2018 ES6 update

  let select = this.refs.selectRef;
  let values = [].filter.call(select.options, o => o.selected).map(o => o.value);

2 Comments

will use this to substitute for my answer as well, since selectedOptions is not supported in IE
Here's a much cleaner, es6 way to do it :) [].filter.call(this.refs.selectRef.options, o => o.selected).map(o => o.value);
9

In the case you would like to keep track of the selected options while the form is being completed:

handleChange(e) {
    // assuming you initialized the default state to hold selected values
    this.setState({
        selected:[].slice.call(e.target.selectedOptions).map(o => {
            return o.value;
        });
    });
}

selectedOptions is an array-like collection/list of elements related to the DOM. You get access to it in the event target object when selecting option values. Array.prototype.sliceand call allows us to create a copy of it for the new state. You could also access the values this way using a ref (in case you aren't capturing the event), which was another answer for the question.

2 Comments

As mentioned in one of my other comments, browser support for selectedOptions seems pretty sketchy. But it would probably be the ideal solution if the support was there.
Ah true, it seems like IE still does not support this
4

The following worked for me

var selectBoxObj = React.findDOMNode(this.refs.selectBox)
var values = $(selectBoxObj).val()

2 Comments

Correct, I used JQuery to retrieve the multiple values.
You don't need react dom or jQuery. Just retrieve the value from the onChange event
2

Another way to do it:

handleChange({ target }) {
    this.props.someCallback(
       Array.prototype.slice.call(target.selectedOptions).map(o => o.value)
    )
}

Comments

2

After spending a lot of time with this topic, and seeing that we are now all using mostly hooks and newbies may have never even dealt with classes in reactjs. I decided to update this answer so that people can easily deal with using vanilla javascript and ES6. I am also including a link to codesandbox so that people can play with the code and modify it to their application.

Here is the code, it has excessive console logs so that people can figure out what is doing what in the code and how it is doing it.

import "./styles.css"
import {useRef} from 'react'


export default function App() {

  const handleSubmit = async(e) => {
    e.preventDefault()
    let selected = [...recipeRef.current.options]
                .filter(option => option.selected)
                .map(option => option.value)
    console.log(new Date(), ' recipeType: ', selected)
    console.log(new Date(), 'with ref: ', [...recipeRef.current.options]);
    [...recipeRef.current.options].map(option => console.log(option,'; ',option.selected))
    console.log([...recipeRef.current.options].filter(option => option.selected))
    console.log([...recipeRef.current.options].filter(option=>option.selected).map(option => option.value))
  }
  const recipeRef = useRef()

  return (
    <div className="App">
      <h1>Hello CodeSandbox example for select Multiple by Julio Spinelli</h1>
      <form onSubmit={handleSubmit}>
          <div className="mb-6">
              <label htmlFor="text" className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300">Name of the Recipe</label>
              <input type="text" id="text" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required ></input>
          </div>
          <div className="mb-6">
              <label htmlFor="recipeType" className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300">Recipe Type</label>
              {/* <input type="text" id="text" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required onChange={(e)=>{setRecipeType(e.target.value)}} value={recipeType}></input> */}
              <select name="recipeType" id="recipeType" ref={recipeRef} multiple={true} size={3}> {/* Step 2 - Add the reference to `select` element */}
                <option value="unselected">unselected</option>
                <option value="Grill">Grill</option>
                <option value="Steak">Steak</option>
                <option value="Pizza">Pizza</option>
              </select>
          </div>
          <div className="flex items-start mb-6">
              <div className="flex items-center h-5">
              <input id="remember" aria-describedby="remember" type="checkbox" className="w-4 h-4 bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" ></input>
              </div>
              <div className="ml-3 text-sm">
              <label htmlFor="remember" className="font-medium text-gray-900 dark:text-gray-300">Recipe uploaded?</label>
              </div>
          </div>
          <button type="submit" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save</button>
      </form>
    </div>
  )
}

Comments

2

Using event object:

handleChange(e) {
    let selectedOptions = e.target.selectedOptions

    // selectedOptions looks like this:
    // {
    //      '0': <1st selected HTMLOptionElement>,
    //      '1': <2nd selected HTMLOptionElement>,
    //      '2': <3rd selected HTMLOptionElement>
    // }

    let newArrayOfSelectedOptionValues = Object.values(selectedOptions).map(
        opt => opt.value
    )

    this.props.someCallbackFromParent(newArrayOfSelectedOptionValues);
}

So the full ??? looks like this:

Object.values(e.target.selectedOptions).map( opt => opt.value )

Comments

1

Try this one:

dropdownChanged = (event) => {
    let obj = JSON.parse(event.target.value);
    this.setState(
        {
            key: obj.key,
            selectedName: obj.name,
            type: obj.type
        });
}


<select onChange={this.dropdownChanged} >
<option value={JSON.stringify({ name: 'name', key: 'key', type: "ALL" })} >All Projetcs and Spaces</option></select>

1 Comment

Add some more explanation to your solutions.
1
onChange={(e) => setSelectedTaxes(e.map((tax) => tax.value))}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.