Contact Info

Jaded Studio

Thoreau Blvd.
O'Fallon, MO 63366
phone: 618.219.5249
email: send me accolades

Find Me On...

Sidebar

Fast Searchable Dropdown

03 Nov 2016

The input field is probably one of the most problematic usability nightmares that most enterprise-class applications have to deal with. Agree? Then take a seat and let your ole buddy show you my way.

While working on a project I ran into one of these nightmares. The programmer has been tasked with putting 6000 values in a single dropdown.So he did. The designer threw a sh*t fit, because that's what they do. So here I am trying to explain to a product owner, a designer, project managers, and a product owner that this is not the way to go about this.

So I preferred to go another route. One that gives the users an input field and validate against the list of possible entries. Give the user an input field and a drop down. Input filters dropdown, dropdown guides users.

...So here's how I would do it.

Step 1: react-select

Watson's react-select library gives you input fields with dropdowns. Users can use the input field or use the dropdown.

  import Select from 'react-select';
  import 'react-select/dist/react-select.css';
  const options = [
    // ...
    { value: 'Stanford University', label: 'Stanford' },
    // ...
  ];
  const field = ({ options }) => (
    <Select
        name="university"
        value="one"
        options={options}
        onChange={val => console.log(val)}
    />
  );

The component does everything: input field, styled non-vanilla dropdown, mouse interaction, keyboard shortcuts, filtering. The only gotcha is that options have to be an array of { value, label } objects. Even if both value and label are the same, I tried.

A few seconds to render the dropdown. A few seconds to filter. The browser's UI thread blocked, and you can't even see what you're typing. 5,258 entries is a lot of entries

Step 2: react-virtualized-select

react-virtualized-select solves the first problem – opening the dropdown. It's a higher order component that does a thing and then your thing works better.

It implements paging and hides it behind scroll events. Only a few elements render at a time, and everyone's life is better.

Here's how you use it:

  import Select from 'react-virtualized-select';
  import 'react-select/dist/react-select.css';
  import 'react-virtualized/styles.css'
  import 'react-virtualized-select/styles.css'
  const options = [
    // ...
    { value: 'Stanford University', label: 'Stanford' },
    // ...
  ];
  const field = ({ options }) => (
    <Select
        name="university"
        value="one"
        options={options}
        onChange={val => console.log(val)}
    />
  );

We changed the import Select from to usereact-virtualized-select and… that's all. It opens quickly, and I was typing that whole time that nothing was happening. Browser's UI thread still blocking.

Step 3: react-select-fast-filter-options

react-select-fast-filter-options is practically too long to mention in a tweet, and it solves the second problem: fast search.

It builds an index of your options and uses advanced computer sciencey algorithms discovered some time in the 60's, probably. We rarely have enough data to worry about actual computer science on the front end, but sometimes we do. Here's how you use it:

  import Select from 'react-virtualized-select';
  import createFilterOptions from 'react-select-fast-filter-options';
  import 'react-select/dist/react-select.css';
  import 'react-virtualized/styles.css'
  import 'react-virtualized-select/styles.css'
  const options = [
    // ...
    { value: 'Stanford University', label: 'Stanford' },
    // ...
  ];
  const filterOptions = createFilterOptions({ options });
  const field = ({ options }) => (
    <Select
        name="university"
        value="one"
        options={options}
        filterOptions={filterOptions}
        onChange={val => console.log(val)}
    />
  );

We added a filterOptions prop to Select, which specifies a custom filter implementation, and we used createFilterOptions to instantiate that implementation. No need to worry about how it actually works because It Just Works™.

Looks good, works good. Faster even than the vanilla browser implementation. The only gotcha is that you have to pass the same options to both Select and createFilterOptions. Dynamically generating { value, label } objects from an array won't work.

The good news is that the memoization MobX does for @computedvalues is good enough, so you can do something like this:

class FormData {
    @observable universities = ['Stanford', 'UVA', ...];
    @computed get options() {
        return this.universities.map(name => ({ value: name, label: name }));
    }
    @computed get filterOptions() {
        const options = this.options;
        return createFilterOptions({ options });
    }
}

I don't know if it would work with Redux. As long as you're careful about the reference thing, you should be fine.