1

I'm quite new to reactjs, so I wrote a fairly simple page to test a 'dynamic' <select> options and how to get the selected value. What I wanted to do was on submit, be able to get the selected values by iterating through the this.refs but I'm getting undefined values. I'm not sure how to make it work in a controlled manner using the handle change so I opted to go the uncontrolled approach. However I'd like to know how to do it both ways. Or is there a neater/preferred way to get the selected values on form submit?

Also in the rendering of the select, why can't I add a ref={this.props.formId} there instead of on the OptionsList? If i remove the ref on OptionsList and have it in the select then it doesn't recognise any select inputs.

var data1 = [
  { Id: 1, Name: "Option 1", Value: "Value 1" },
  { Id: 2, Name: "Option 2", Value: "Value 2" },
  { Id: 3, Name: "Option 3", Value: "Value 3" }
];

var data2 = [
  { Id: 4, Name: "Option 4", Value: "Value 4" },
  { Id: 5, Name: "Option 5", Value: "Value 5" },
  { Id: 6, Name: "Option 6", Value: "Value 6" }
];

var FormOptionList = React.createClass({
    handleSubmit: function (e) {
        e.preventDefault();

        for (var ref in this.refs) {
            console.log(this.refs[ref].value);
        }

        return;
    },
    render: function () {
        return (
          <div className="formList">
              <form onSubmit={this.handleSubmit}>
                  <div>
                    <OptionsList key="ListA" formId="ListA" options={this.props.data1} ref="ListA" />
                    <br />
                    <OptionsList key="ListB" formId="ListB" options={this.props.data2} ref="ListB" />
                  </div>
                  <input type="submit" value="Post" />
              </form>
          </div>
      );
    }
});

var OptionsList = React.createClass({
    //getInitialState: function () {
    //    return {
    //        options: [],
    //        selectValue: 'Option 1'
    //    };
    //},
    //handleChange: function(e) {
    //    this.setState({ selectValue: e.target.value })
    //},
    getInitialState: function () {
        return {
            options: []
        };
    },
    render: function () {
        var optionsList = this.props.options.map(function (option) {
            return (
                <option key={option.Id} value={option.Value}>{option.Name}</option>
            )
        });
        return (
            <select>
                {optionsList}
            </select>
        );
    }
});

ReactDOM.render(
  <FormOptionList data1={data1} data2={data2} />,
  document.getElementById('content')
);
2
  • As @Vladimir says. Write a getter in your OptionsList: jsfiddle.net/ufL3071m Commented Jul 1, 2016 at 12:59
  • this is helpful, thanks Commented Jul 1, 2016 at 13:13

3 Answers 3

2

Using refs is not the best way to achieve what you want.

A good way would be

1º Add a method in your parent component to store the values of your <OptionList> components

handleSelectChange(select,value){
    this.setState({[select] : value})
}

2º Pass handleSelectChange to <OptionList> as a prop

<OptionsList key="ListA"
     formId="ListA" options={this.props.data1}
     onChange={this.handleSelectChange} />

3º Pass the <select> value to the parent component using onChange + handleSelectChange

var OptionsList = React.createClass({
    render: function () {
        var optionsList = this.props.options.map((option) => {
            return (
                <option key={option.Id} value={option.Value}>{option.Name}</option>
            )
        });
        return (
            <select onChange={(e) => this.props.onChange(this.props.formId,e.target.value)}>
                {optionsList}
            </select>
        );
    }
});

full working example

Sign up to request clarification or add additional context in comments.

8 Comments

How would you handle this if you have X number of OptionsList, i.e. not hard coding ListA, ListB in the getInitialState?
You don't need the initial state, you can set the initial value in your OptionList componentDidMount event and it'll be added automatically to your parent state.jsfiddle.net/69z2wepo/47498
I see, thanks. What if I need to set a custom initial state, would that be handled in OptionsList? Also with react, are you just propagating the onChange event from OptionsList into FormOptionList OptionsList's onChange event, would this be standard practice if there's a parent/child relationship? Let's pretend I have a <TextboxList />, I'd have an onChange event in there, and in the parent another onChange and use something like this.handleTextboxChange?
Yes, the initial state would be handled in OptionList componentDidMount/componentWillMount (I updated the jsfiddle). About the second question, it's a good practice, in React there are two types of components, the "container" components and the "presentational" components. It's a bit long to explain but you can read about it here, medium.com/@dan_abramov/…
Thanks this is very helpful.
|
1

The reason you're getting undefineds is because what you have in your refs are instances of your custom component OptionsList, not DOM elements. That applies in general in React, if you put a ref on a DOM element (<div ref="myDiv" />), then this.refs.myDiv will get you the actual physical DOM element for this div. Conversely, if you put a ref on a custom component, like you did, then the ref holds an instance of that component. OptionsList has no method or property called value, hence the undefineds. This should also address your second question - each component has its own refs - when you put a ref on the select in OptionsList, it will only be accessible within OptionsList.

In order to make this work, you need to expose some API on OptionsList to get the value and then use that, e.g.

{
  ...
  render(){ 
    return (
            ...
            <select ref='select'>
                {optionsList}
            </select>
        );
 },

  getValue(){ return this.refs.select.value; }
}

And then in FormOptionList: console.log(this.refs[ref].getValue());

Doing this in a controlled style would involve defining a value and onChange prop on the OptionsList - you would more or less just pass those to the select. Then in FormOptionList (or in one of its ancestors), you would need to have a state with the value and use OptionsList's value and onChange to keep this value in sync with the select.

Hope that makes sense :-)

2 Comments

I made a note of this on the second sentence, lets say I remove ref from the OptionsList and in the select I have <select ref={this.props.formId}> now in the getValue(), how do I get the value when its a prop? return this.refs[this.props.formId].value ?
Yes, that should still work, but I would be careful about having a prop dictate the name of the ref, since the prop can change in time. It's generally better to use a static value that you have under control.
0

In your select object (assuming you are using express body-parser) you need to assign a name attribute, for example, look at my code

    <select required name='gender' className={classes.selectBox} >
          <option value='NotSelected' >-------</option>
          <option value='Male' >Male</option>
          <option value='Female'>Female</option>
    </select>

to get the value of this input value run the following in your POST handler

let {gender, <any other name attributes in your POST form>} = req.body;

and there you go! you get a JSON object with a key as the name and the value associated with it.

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.