2

Angular 1.X has ng-options for the choices in a select dropdown, each item being an object. In plain HTML, the value of an option can only be a string. When you select one option in Angular, you can see the actual selected object while in plain html, you can only get that string value.

How do you do the equivalent of that in React (+Redux)?

2
  • Check out react-select. You can also create an array of selects to be the children of an option element.
    – Andy_D
    Commented Feb 22, 2017 at 1:18
  • @Andy_D thanks for the suggestion. I am actually using Semantic UI React (react.semantic-ui.com/modules/dropdown) and the issue is the value of each option can only be a string. I kind of found a way around that by stringifying the object into the value. I looked at the source code for react-select and they do that. The thing is I have to do JSON parse for every onchange function I have. I was wondering if there's a better approach. Another idea is use the array index as the value and access all the options, then narrowing down using the index.
    – nbkhope
    Commented Feb 22, 2017 at 1:26

2 Answers 2

0

I came up with a solution that does not use JSON.stringify / parse for the value of the select React element nor does it use the index of the array of choice objects as the value.

The example is a simple select dropdown for a person's gender -- either male or female. Each of those choices is an actual object with id, text, and value properties. Here is the code:

MySelect component

import React, { Component } from 'react';

class MySelect extends Component {
  onGenderChange = (event) => {
    // Add the second argument -- the data -- and pass it along
    // to parent component's onChange function
    const data = { options: this.props.options };
    this.props.onGenderChange(event, data);
  }

  render() {
    const { options, selectedOption } = this.props;

    // Goes through the array of option objects and create an <option> element for each
    const selectOptions = options.map(
      option => <option key={option.id} value={option.value}>{option.text}</option>
    );

    // Note that if the selectedOption is not given (i.e. is null),
    // we assign a default value being the first option provided
    return (
      <select
        value={(selectedOption && selectedOption.value) || options[0].value}
        onChange={this.onGenderChange}
      >
        {selectOptions}
      </select>
    );
  }
}

App component that uses MySelect

import _ from 'lodash';
import React, { Component } from 'react';

class App extends Component {
  state = {
    selected: null
  }

  onGenderChange = (event, data) => {
    // The value of the selected option
    console.log(event.target.value);
    // The object for the selected option
    const selectedOption = _.find(data.options, { value: parseInt(event.target.value, 10) });
    console.log(selectedOption);

    this.setState({
      selected: selectedOption
    });
  }

  render() {
    const options = [
      {
        id: 1,
        text: 'male',
        value: 123456
      },
      {
        id: 2,
        text: 'female',
        value: 654321
      }
    ];

    return (
      <div>
        <label>Select a Gender:</label>
        <MySelect
          options={options}
          selectedOption={this.state.selected}
          onGenderChange={this.onGenderChange}
        />
      </div>
    );
  }
}

Lodash is used to look up the choice object in the array of choice objects inside the onGenderChange function in the App component. Note that the onChange passed to the MySelect component requires two arguments -- an extra data argument is added in order to be able to access the choice objects ("options"). With that, you can just set the state (or call an action creator if using Redux) with the choice object for the selected option.

0

I run into this same situation while migrating a angular 1 app to react. And I felt it is a much needed feature that I couldn't find so here I leave my implementation of NgOption in react using bootstrap (you can can change that to whatever you are using) for anybody that's missing the goo'old angular 1:

import React from 'react';
import { Form, InputGroup } from 'react-bootstrap';

interface props<T, U>{
  options: Array<T>,
  selected?: U,
  onSelect: (value: T) => any,
  as: (value: T) => string,
  trackBy: (value: T) => U,
  disabled?: boolean
}

type state = {}

export default class NgOptions extends React.Component<props<any, any>, state> {
  componentDidMount() {
    if (this.props.selected) {
      this.props.onSelect(this.props.options.find((o) => this.props.trackBy(o)===this.props.selected))
    }
  }
  
  public render() {
    return ( <InputGroup>
      <Form.Control as="select"
                    disabled={this.props.disabled}
                    onChange={(e) => this.props.onSelect(this.props.options.find((o) => this.props.trackBy(o)===e.target.value))}>
        {this.props.options.map( option =>
          <option key={this.props.trackBy(option)}
                  selected={this.props.selected===this.props.trackBy(option)}
                  value={this.props.trackBy(option)}>{this.props.as(option)}</option>
        )}
      </Form.Control>
    </InputGroup>
    )
  }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.