How to Search Backend API Using React-Select

2017-03-14

The following article discusses the use of react-select to setup an HTML select box which connects to the server for getting options and allows you to filter backend API results using user’s input.

You can go to its Github Repo for more documentation.

To fetch results asynchronously (from an API), you will have to use Select.Async

1
2
3
4
5
6
7
8
9
10
<Select.Async
name="typeId"
value={this.state.typeId}
valueKey="id"
labelKey="type"
matchPos="any"
matchProp="any"
loadOptions={getTypeOptions}
onChange={this.updateType}
/>
  • value: If you do not specify value attribute, the select box will not show the selected value
  • valueKey: Lets you override the default key for the value. By default, it takes value key from the object
  • labelKey: Lets you override the default key for the label. By default, it takes label key from the object
  • loadOptions: Takes the function that returns a promise which further returns the data for options

The select box also lets the user filter option results by typing in the select box. The following options lets you modify how that filtering takes place:

  • matchPos: Possible options are any and start. any matches the term from anywhere inside the word while start tells the plugin to match from the start of the word. By default its set to any
  • matchProp: Possible options are any, label, value. any searches on both the value and label keys whereas specifying anyone only searches over those keys. By default its set to any

updateType will be called whenever the user selects an input (either by pressing enter or by clicking on the option). Implementation of updateType:

1
2
3
updateStorageType(value) {
this.setState({typeId: value.id});
}

getTypeOptions calls the API and returns a promise:

1
2
3
4
5
getTypesOptions () {

return axios.get(`/api/v1/restaurants/types`)
.then(res => {return {options: res.data}});
}

Note that loadOptions does not take an array of options directly. It requires an object with options key which contains the array of options

Search Backend API

To use user’s typed characters to filter results from backend, you can bind the function provided to loadOptions with this:

1
2
3
4
5
6
7
8
9
<Select.Async
name="storageTypeId"
value={this.state.storageTypeId}
valueKey="id"
labelKey="type"
matchPos="any"
loadOptions={getStorageOptions.bind(this)}
onChange={this.updateStorageType}
/>

This passes user’s input to getStorageOptions which you can use to query backend:

1
2
3
4
5
getTypesOptions (searchTerm) {

return axios.get(`/api/v1/restaurants/types?search=${searchTerm}`)
.then(res => {return {options: res.data}});
}

Prevent Unnecessary API Calls

When you type something in the search box, it brings fresh data from the backend and then filters the results. However, if you are not filtering the results using user’s typed input and returning complete options set in your API call, making new API calls every time the user types something is redundant. So, to prevent additional queries, you can return complete: true with the options:

1
2
3
4
5
6
7
getTypesOptions () {

return axios.get(`/api/v1/types`)
.then(res => {
return {options: res.data, complete: true}
});
}

Create New Option

react-select also lets you create a new option from within the select box. To use this functionality asynchronously, you will have to use AsyncCreatable from react-select:

import {AsyncCreatable} from 'react-select

And use it in JSX like this:

1
2
3
4
5
6
7
8
<AsyncCreatable
name="typeId"
value={this.state.typeId}
valueKey="id"
labelKey="type"
loadOptions={getStorageOptions}
onChange={this.updateStorageType}
/>

The rest of your code works the same as from previous example. The only thing that is changed is that we are using AsyncCreatable instead of Select.Async. However, there are a couple of more attributes added which we will explain below.

Handling Creation of New Option

Sadly, onNewOptionClick wasn’t working for me. But I noticed that whenever a new tag is created the object passed to the handler contains className but if the user selects an option it doesn’t contain that attribute. So, I ended up coming up with a work around in onChange handler:

1
2
3
4
5
6
7
updateStorageType (value) {
if (value.className) {
// handle newly created.
} else {
this.setState({storageTypeId: value.id});
}
}

Hope this helps.