DEV Community

Cover image for Implementing a Searchable, Async Dropdown in React
wlytle
wlytle

Posted on

Implementing a Searchable, Async Dropdown in React

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 />
Enter fullscreen mode Exit fullscreen mode

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,
   },
 ]   
Enter fullscreen mode Exit fullscreen mode

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)}
       />
...
Enter fullscreen mode Exit fullscreen mode

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}
...
Enter fullscreen mode Exit fullscreen mode

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))
       }
 };

Enter fullscreen mode Exit fullscreen mode

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)}
      />
Enter fullscreen mode Exit fullscreen mode

Gif of simple async search bar dropdown

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}
...
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

I hope you found this helpful in implementing this beautiful library into your projects!

Discussion (0)