Implementing a Searchable, Async Dropdown in React
wlytle
Posted on January 25, 2021
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.
- 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.
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!
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.
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.
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.
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.
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.
// 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)}
/>
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
.
...
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
Here is the full code for reference:
// 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!
Posted on January 25, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.