Creating a Twitter-like search component in React

Think for example Twitter's search box. As the search results come in, a dropdown menu renders and user can select any of the items. How can I build this in react?

Let's try to built this kind of component! To keep things simple, I'll skip the styling and actually loading the data from the Twitter API, and instead focus on the "dumb" component for now.

Example Twitter search

Pick an already made component for this

These type of components are called “autocomplete” or “typeaheads”. Actually, there’s isn’t just one way to call them, as Kent Dodds was rightly pointed out a while ago:

So what options do you have? I managed to find 3 libraries that provide "autocomplete" functionality:

For the purpose of this tutorial, I’ll use Downshift.

Create a dumb component for your search box

This means you should first create a component that works as you expect and not be bothered by how you will get the data into it.

When we’re done, we want to have a component that we can use like this:

<SearchBox 
          placeholder="Search for .."
          items={['React Vienna', 'React Finland', 'Jest', 'Enzyme', 'Reactjs']} />

.. and the output will look like:

Example Twitter search

So, without further ado, let’s get to building it :)

1. Add downshift to your project

yarn add downshift

The component should accept as props:

  • the list of items we can search through
  • the placeholder to show in the input before the user typed anything
// components/SearchBox/index.js
import React from 'react';
import Downshift from 'downshift';

const SearchBox = ({ items, placeholder }) => {
    return (
      // We need to put downshift here :)
    )
  }

export default SearchBox;

3. Add downshift

The way downshift works is that it gives us a prop called render into which we can pass a function describing how we want our dropdown to look. This is great news, as it means we have full control over the rendered HTML!

<Downshift
  render={(propsFromDownshift) => (
    <div>
      // We can render anything here and we have access to details about 
      // the state of the component through `propsFromDownshift`
    </div>
  )}
/>

An autocomplete component usually consists of 3 parts:

  • an input
  • a container for showing the suggestions
  • a suggested item

Let’s render those step by step. I’ll go over what props we use from Downshift as we go, but if you want to see a full list available, check full list of render props.

Adding the input

Since our search box is rather easy, we’ll just use an HTML input tag for searching:

<input type="text" />

Downshift gives us access to a list of props we can add to our input through the getInputProps function passed into render.

Here’s what I mean by that:

<Downshift
  render={({ getInputProps }) => {
    console.log(getInputProps());
  }}
/>
Downshift input props

If we want to add more props that the ones from Downshift, we can do that by passing them as arguments to that function:

getInputProps({ placeholder: 'Type here' });

So, let’s go ahead and add the input:

// components/SearchBox/index.js
import React from 'react';
import Downshift from 'downshift';

const SearchBox = ({ items, placeholder }) => {
    return (
      <Downshift
        render={({
          getInputProps,
        }) => (
          <div>
            <input {...getInputProps({ placeholder })} />
          </div>
        )}
      />
    )
  }

export default SearchBox;

Adding the container for showing the suggestions

We need a way to tell when the container is visible and when not - based on what the user typed in the box.
For this, we have access to a prop named isOpen inside the render function. Let’s use it to add a suggestions box to our component:

const SearchBox = ({ items, placeholder }) => {
    return (
      <Downshift
        render={({
          getInputProps,
          isOpen,
        }) => (
          <div>
            <input {...getInputProps({ placeholder })} />
            {isOpen ? (
              <div style={{border: '1px solid #ccc'}}>
                  // Our suggestions will go here!
              </div>
            ) : null} // Return null if the box isn't open
          </div>
        )}
      />
    )
  }

Showing the suggested items

This is the trickiest part of all so far, so I’ve annotated the code line by line:

const SearchBox = ({ items, placeholder }) => {
    return (
      <Downshift
        render={({
          getInputProps, // Props to pass to our input
          getItemProps,  // Props to pass into each of the suggested items
          isOpen,        // Whether the "suggestions box" is visible or not
          inputValue,    // Value that the user typed in the search box
          selectedItem,  // Item that is currently selected in the list (when hovering)
          highlightedIndex, // Index of the item currently selected in the list
        }) => (
          <div>
            // The search box where the user types
            <input {...getInputProps({ placeholder })} />
            {isOpen ? (
              <div style={{border: '1px solid #ccc'}}>
                {items // Items are passed in as a prop 
                       //-> so it's a list of all elements, unfiltered
                  .filter( // that's why we first filter the list
                    i =>
                      // show item `i` if:
                      !inputValue || // the user didn't type anything in the box
                      // OR item `i` contains the text from the user (`inputValue`)
                      i.toLowerCase().includes(inputValue.toLowerCase()),
                  )
                  // then, for each filtered item ..
                  .map((item, index) => (
                    // output a <div> ..
                    <div
                      {...getItemProps({item})} // .. using the props from `render`
                      key={item}
                      style={{
                        backgroundColor:
                          highlightedIndex === index ? 'gray' : 'white',
                        fontWeight: selectedItem === item ? 'bold' : 'normal',
                      }}
                    >
                      {item} // .. and containing the name of the suggestion
                    </div>
                  ))}
              </div>
            ) : null}
          </div>
        )}
      />
    )
  }

That’s it!

You should be able to use the component in your app by just passsing in a list of items and a placeholder! And of course, you should probably tweak the styling a bit :P

You can also find the full source code on Github.

Final search box