Implementing a Searchable, Async Dropdown in React
Tags: react, reactselect, learning, forms
Published: 2021-01-25 18:44:24

## Motivation
I've been working on an app with a React frontend and a Rails backend where a user can create a trip via an interactive map. I wanted to allow a user to invite collaborators to help build out their trip. On first thought, I figured I would load all of my users into state and then query that to get quick responsive feedback. However, I realized I want to search my users and this can, and hopefully, will become a large dataset. This makes me hesitant to load too much data into state as it is all stored on RAM. The solution, an elegant and simple Library called **`react-select`**. 

This walkthrough assumes some comfort with React, hooks, and controlled forms.

## TLDR
* `react-select` provides a convenient select component that works nicely with AJAX requests.
* Good documentation available [Here](https://react-select.com/home).
* Code example at the end of the blog.

## Installation 
Assuming you have a react app already spun up installation is nice and simple. 
run `npm install react-select`

It's important to note that `react-select` has a robust library of different options. I'll cover some of them but will mostly be focused on the async features of the library. Again see the full docs [Here](https://react-select.com/home).

With that said, make sure to include `import AsyncSelect from "react-select/async"` at the top of your component.

##Usage

They make it so simple! 
```jsx
return <AsyncSelect />
```
This will give us a nicely styled search bar with a dropdown... But it's not wired up to any logic.

## A Short Digression About Data

Before we get too far into the weeds, we need a quick primer on the data structure `react-select` is built to expect. `react-select` expects data to be an array of objects with each object having keys `label` and `value`. The `label` value is the information that is displayed and the `value` value indicates which information is selected (clear as mud right?). Let's look at an example. 
```json
const data = [
  {
    label: "Cheddar",
    value: 1,
  },
  {
    label: "Manchego",
    value: 2,
   },
 ]   
```
In this example, the dropdown menu will show `Cheddar` and `Manchego` when those values meet the search criteria. If a user selects `Manchego` `react-select` will grab the data associated with the object whose `value` corresponds to `2`


## Props

The `AsyncSelect` component takes in a myriad of props that add functionality and styling. The component is set up to work with callbacks **or** promises; we will focus on promises.

### onInputChange

This prop is similar to a simple `onChange` in an input field and will record inputs. This prop can be used to make the select menu a controlled component.
```jsx
import React, { useState } from "react";
import AsyncSelect from "react-select/async";

const [query, setQuery] = useState("");

return <AsyncSelec 
         onInputChange={onInputChange={(value) => setQuery(value)}
       />
...
```

### loadOptions
This is the most important prop. `loadOptions` accepts a function that must return a promise (or callback) and this promise should resolve to be your search data. For my purposes, this promise comes from a fetch request to my rails API with a search parameter set to my sate `query` variable.

```jsx 
const loadOptions = () => {
// You must return this otherwise react-select won't know when the promise resolves! 
    return fetch(`http://localhost:3000/collabs?q=${query}`)
      .then((res) => res.json());
 };
...
loadOptions={loadOptions}
...
```
It's worth noting that the above fetch request returns the results of a search function using the `query` parameter. If you wanted to do all the filtering on the front end you could implement something like this.

```jsx
const loadOptions = () => {
    return fetch(`http://localhost:3000/collabs`)
      .then((res) => res.json())
      .then((data) = > {
          data.filter((ele) => ele.user_name.includes(query))
       }
 };

```
### onChange
The `onChange` prop tells the component what to do with the selected record. I found it very helpful to simply store this in state as well for later use. This way the value can also be set in a callback prop so a parent component can know what was selected.

```jsx
// In parent component
const [collabs, setCollabs] = useState("");
<AsyncSearchBar setCollabs={setCollabs} />

// in async searchbar component
const AsyncSearchBar = ({ setCollabs }) => { 
...
<AsyncSelect
        loadOptions={loadOptions}
        onInputChange={(value) => setQuery(value)}
        onChange={(value) => setCollabs(value)}
      />
```
![Gif of simple async search bar dropdown](https://dev-to-uploads.s3.amazonaws.com/i/2j1m3fwzg8av4ffsytdk.gif)

That's all you really need to get things going! You can see you get a nice loading message while `react-select` is waiting for the promise to resolve.

## Bonus props
There's a lot more to be said about the optional props of `react-select` I'll leave most of that to you, but I do want to go over a few that I found most helpful.

### `getOptionLabel` and `getOptionValue` 

Odds are that your data is not already configured to have `value` and `label` keys. These props help account for that. Both props take a callback that indicates the key in the data that should be used in place of `label` and `value`.

```jsx 
...
        getOptionLabel={(e) => e.user_name}
        getOptionValue={(e) => e.id}
...
```
Here we are telling `react-select` to use the `user_name` key instead of `label` and the `id` key instead of `value`. No Need to reformat data!

## `isMulti`
This is a great prop that only needs to be set to `true`. It allows you to select multiple options from the dropdown.

## `cacheOptions`
This prop also only needs to be set to `true` to be included. It will cache the returns from `loadOptions`. If you retype something in short order or say hit backspace, the component will have access to previous search results and will not fire off more `fetch` requests. 

### Animations! 
`react-select` allows you to wrap most parts of the search bar in custom components which is really nice; we can use this to implement some slick built-in animation styles. First we need to add 
`import makeAnimated from "react-select/animated";` 
to our imports. Then we can use this import to easily create animated wrapper components.
` const animatedComponents = makeAnimated();`
Then we use the components prop like so
`components={animatedComponents}`

## Putting It All Together
![gif of final multi-select async drop down](https://dev-to-uploads.s3.amazonaws.com/i/j2po30yjqjf12xlzlpjw.gif)

Here is the full code for reference: 
```jsx
// In parent component
const [collabs, setCollabs] = useState("");
...
<AsyncSearchBar setCollabs={setCollabs} />

// ./AsyncSearchBar
import React, { useState } from "react";
import AsyncSelect from "react-select/async";
import makeAnimated from "react-select/animated";
import { makeHeader } from "../actions/TripActions";

const AsyncSearchBar = ({ setCollabs }) => {
  //set default query terms
  const [query, setQuery] = useState("");

  //get animated components wrapper
  const animatedComponents = makeAnimated();

 // fetch filteres search results for dropdown
  const loadOptions = () => {
    return fetch(`http://localhost:3000/collabs?q=${query}`)
    .then((res) => res.json());
  };

  return (
    <>
      <AsyncSelect
        cacheOptions
        isMulti
        components={animatedComponents}
        getOptionLabel={(e) => e.user_name}
        getOptionValue={(e) => e.id}
        loadOptions={loadOptions}
        onInputChange={(value) => setQuery(value)}
        onChange={(value) => setCollabs(value)}
      />
    </>
  );
};

export default AsyncSearchBar;
```
I hope you found this helpful in implementing this beautiful library into your projects!