Autocomplete Search Feature using Downshift

In Wesbos Advanced React lesson, the module 10 is on implementing the Search feature to search for products across the site.

The Stack: KeystoneJS (auto generates Backend GraphQL APIs), NextJS, Apollo GraphQL, Downshift (autocomplete widget).

The 2 things for implementing this Search feature are:

  1. useLazyQuery from Apollo GraphQL Queries
  2. Downshift API

GraphQL Query

KeystoneJS auto creates graphQL queries based on our schema. E.g. if we have a Products schema. One of the generated queries is allProducts which allows us queries for all products in the database. Also, we are given a number of ways to filter, sort, limit our items e.g. the where filters.

We can insert the search input into the where filters for querying just the matched products from the database.

While most graphQL queries get called when the component gets rendered, we want this search query to be called when the user actually starts searching. i.e. we want this search query to be executed manually at a later time. Therefore, we can use useLazyQuery for executing the search query! When useLazyQuery is called, it does not immediately executes the associated query. Instead it returns a function that we can attach to our onChange or onClick event listeners!

No Data Returned?

If there is no data returned from the query and we have tried divide and conquer in the debugging process to make sure:

  • input into the query was correct- e.g. hardcoding the input
  • graphQL API does return correct data —e.g. hardcoding inputs in graphQL playground
  • queries are called as expected — e.g. console.log in the function

Then, it could be a case with CACHE!

We don’t want to save the search data in the Apollo Cache as the filtered product data is likely to be different as we search for different products.

In useLazyQuery , we need to set fetchPolicy: 'no-cache' !

Downshift Autocomplete Widget

Downshift is a popular, well tested ARIA compliant React autocomplete component. In our case, we can use the useCombobox hook to get a list of APIs for setting up our autocomplete Search input with our custom styles.

APIs: getComboboxProps, getInputProps, getMenuProps…

These prop getters corresponds to the different parts of a dropdown selection box as seen from the diagram above.

Note: all the props that are to be placed into the component directly should be placed in the prop getters instead.

// e.g.
<input
{...getInputProps({
type: 'search',
id: 'search'
})}
/>

useCombobox: 3 inputs here

  • onInputValueChange(): this method if called when user starts typing in the Search box. We call the useLazyQuery here to get a list of matching products to be displayed in the dropdown menu!
  • onSelectedItemChange(): this method is called when user selects an item in the dropdown menu. We want to take the user to the corresponding product page so we update the routes here!
  • itemToString(): Downshift needs a string representation of the item. If our item is an object structure, we need to return a string representation for Downshift

Other Things to note

  • getItemProps() needs to be set in order for onSelectedItemChange() to be called
  • The highlighted styling can be enabled by matching the highlightedIndex from useCombobox with the item.index
  • To make the dropdown menu hide when we click away, as can use the isOpen property from useCombobox. Only When isOpen is true, we run through items.map
  • To handle to noFound case, we can check for the condition (isOpen && !items.length && !loading)
  • If we are using Downshift in SSR e.g. NextJS, we need to synchronise IDs generated in server and client using resetidcounter.

An example of the completed Search Component can be seen in the solution code.

A software engineer passionate about learning and growth