Lewis Tyler

Dynamic, re-usable multiple select component with react-select

React-select is a great library for creating select elements with modern functionality and styling that the standard HTML <select> just can’t do. React-select also has great docs, which make integrating it into your project a breeze.

However one thing that is not covered in the docs is how to create a re-usable select component that can manage selections from multiple selects on the same page, with a single onChange handler.

Allow me to show you how I solved the problem:

selectComponent.js

import React, { Component } from "react";
import Select from "react-select";

export default class SelectComponent extends Component {
  constructor(props) {
    super(props);

    this.state = {
      selectedOptions: null
    };
  }

  handleChange = (...args) => {
    this.props.addSelectedOption(this.props.name, ...args);
  };

  render() {
    return (
      <form>
        <Select
          {...this.props}
          onChange={this.handleChange}
          value={this && this.state ? this.state.option : null}
          ref={(input) => {
            this.selectField = input;
          }}
        />
      </form>
    );
  }
}

Here we set up our SelectComponent which we can re-use throughout our templates. After importing the base Select from "react-select" we set up an empty state to hold selectedOptions for this individual select - this is mainly for showing the selected options on the frontend. We’ll also make use of {...this.props} so we can easily receive props from wherever it’s used and make the component as versatile as possible.

We’ll also set up an handleChange function which contains an addSelectedOption() function, as a prop, from our main template. Let’s see how that works:

// index.js

import React from "react";
import { render } from "react-dom";
import SelectComponent from "./SelectComponent";

const selects = [
  {
    name: "flavour",
    options: [
      { value: "mint", label: "Mint" },
      { value: "chocolate", label: "Chocolate" },
      { value: "strawberry", label: "Strawberry" }
    ],
    isMulti: true
  },
  {
    name: "size",
    options: [
      { value: 1, label: "One Scoop" },
      { value: 2, label: "Two Scoops" },
      { value: 3, label: "Three Scoops" }
    ],
  }
];

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      selectedOptions: {}
    };
  }

  addSelectedOption = (name, options) => {
    this.setState(({ selectedOptions }) => ({
      selectedOptions: {
        ...selectedOptions,
        [name]: options
      }
    }));
  };

  componentDidUpdate() {
    console.log(this.state);
  }

  render() {
    return (
      <div>
        {selects.map((select, index) => {
          return (
            <SelectComponent
              key={index}
              isMulti={select.isMulti}
              name={select.name}
              options={select.options}
              addSelectedOption={this.addSelectedOption}
            />
          );
        })}
      </div>
    );
  }
}

Here we have set up a main selectedOptions object which will be used to store all selections from all selects for use in our app.

When the select is changed, the handleChange() function we created in SelectComponent.js calls our main addSelectedOption() function, passing the name of the select (in this case, flavour and size) and the selected options - this data is consumed in addSelectedOption() with setState, adding name and selected options to selectedOptions: {}.

And there you have it - a nice elegant way to track user selections in your app. This methodology could also be extended to other field types in a scenario, such as a listings browser, where different types of filters are required.

See the full example on codesandbox.