UI Libraries / Autocomplete / Including Multiple Result Types

Including Multiple Result Types

Following this tutorial requires an Algolia application with the Query Suggestions feature enabled.

While autocompletes with suggested searches is a ubiquitous search experience, rich multi-category autocompletes are becoming more and more popular.

For example, if you search for something in your email inbox, your results could contain not just email threads, but also contacts, attachments, and more. Ecommerce stores often show suggested searches, products, blog posts, brands, and categories all in one autocomplete.

It’s best to display different results types in different sections. This implicitly gives users a better understanding of what the items are, and what will happen if they select an item.

The Autocomplete library lets you mix different item types in one autocomplete and customize their display. To do so you need to return multiple sources in the getSources option. This tutorial outlines how to combine static predefined items, recent searches] and Query Suggestions in one autocomplete.

Though it’s not necessary, it uses plugins for each source. Instead, you could add different sources directly in getSources. However, it’s recommend to encapsulate source logic in a plugin since this makes it modular, reusable, and sharable.

Prerequisites

This tutorial assumes that you have:

  • a populated Query Suggestions index
  • existing markup containing an input element where you want to implement the autocomplete dropdown
  • front-end development proficiency with HTML, CSS, and JavaScript

Getting started

First, begin with some boilerplate for the autocomplete implementation. Create a file called index.js in your src directory, and add the boilerplate below:

1
2
3
4
5
6
7
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  container: '#autocomplete',
  plugins: [],
  openOnFocus: true,
});

This boilerplate assumes you want to insert the autocomplete into a DOM element with autocomplete as an id. You should change the container to match your markup. Setting openOnFocus to true ensures that the dropdown appears as soon as a user focuses the input.

For now, plugins is an empty array, but you’ll learn how to create and add plugins for predefined items, recent searches, and Query Suggestions next.

Adding predefined items

A popular search pattern for autocomplete menus shows predefined search terms as soon as a user clicks on the search bar and before they begin typing anything. This UX provides a guided experience and exposes users to helpful resources or other content you want them to see.

This tutorial describes how to create a plugin to show static, predefined items. In particular, it exposes helpful links the user may want to refer to.

Creating a predefined items plugin

Begin by creating a predefinedItemsPlugin.js file in your src directory, with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const predefinedItems = [
  {
    label: 'Documentation',
    url: 'https://autocomplete.algolia.com/',
  },
  {
    label: 'GitHub',
    url: 'https://github.com/algolia/autocomplete',
  },
];

export const predefinedItemsPlugin = {
  getSources() {
    return [
      {
        sourceId: 'predefinedItemsPlugin',
        getItems({ query }) {
          if (!query) {
            return predefinedItems;
          }
          return predefinedItems.filter((item) =>
            item.label.toLowerCase().includes(query.toLowerCase())
          );
        },
        getItemUrl({ item }) {
          return item.url;
        },
        templates: {
          // ...
        },
      },
    ];
  },
};

Notice that predefinedItemsPlugin has a similar signature as any other autocomplete implementation: it uses the getSources option to return an array of items to display. Each object in the array defines where to get items using getItems. An Autocomplete plugin is an object that implements the AutocompletePlugin interface. To learn more, check out the documentation on building your own plugin.

In this example, getItems returns a filtered array of predefinedItems. The code filters the array to return items that match the query, if it exists. If it doesn’t, it returns the entire array. You can return whatever predefined items you like and format them accordingly. For example, suppose you want to show trending search items instead of helpful links. Then, you can use getItems to retrieve them from another source, including an asynchronous API.

The getItemUrl function defines how to get the URL of an item. In this case, since it’s an attribute on each object in the predefinedItems array, you can simply return the attribute. You can use getItemUrl to add keyboard navigation to the autocomplete menu. Users can scroll through items in the autocomplete menu with the arrow up and down keys. When they hit Enter on one of the predefinedItems (or any source that includesgetItemUrl), it opens the URL retrieved from getItemUrl.

Templates define how to display each section of the autocomplete, including the header, footer, and each item. Templates can return anything that is a valid virtual DOM element (VNode).

This example defines how to display each predefined item with the item template and gives a header for the entire section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
const predefinedItems = [
  {
    label: 'Documentation',
    url: 'https://autocomplete.algolia.com/',
  },
  {
    label: 'GitHub',
    url: 'https://github.com/algolia/autocomplete',
  },
];

export const predefinedItemsPlugin = {
  getSources() {
    return [
      {
        sourceId: 'predefinedItemsPlugin',
        getItems({ query }) {
          if (!query) {
            return predefinedItems;
          }
          return predefinedItems.filter((item) =>
            item.label.toLowerCase().includes(query.toLowerCase())
          );
        },
        getItemUrl({ item }) {
          return item.url;
        },
        templates: {
          header({ items }) {
            if (items.length === 0) {
              return null;
            }
            return (
              <>
                <span className="aa-SourceHeaderTitle">Links</span>
                <div className="aa-SourceHeaderLine" />
              </>
            );
          },
          item({ item }) {
            return (
              <a className="aa-ItemLink" href={item.url}>
                <div className="aa-ItemIcon aa-ItemIcon--noBorder">
                  <svg
                    width="18"
                    height="18"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  >
                    <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
                    <polyline points="15 3 21 3 21 9" />
                    <line x1="10" y1="14" x2="21" y2="3" />
                  </svg>
                </div>

                <div className="aa-ItemContent">
                  <div className="aa-ItemContentTitle">{item.label}</div>
                </div>
              </a>
            );
          },
        },
      },
    ];
  },
};

Adding the predefined items plugin to the autocomplete

All that’s left is to import the newly created predefinedItemsPlugin and add it toplugins in index.js. Once you’ve done that, the file should look like this:

1
2
3
4
5
6
7
8
import { autocomplete } from '@algolia/autocomplete-js';
import { predefinedItemsPlugin } from './predefinedItemsPlugin';

autocomplete({
  container: '#autocomplete',
  plugins: [predefinedItemsPlugin],
  openOnFocus: true,
});

Now, as soon as a user clicks on the search bar, these predefined items appear. Once they begin typing, only predefined items that contain the query remain.

Adding recent searches and Query Suggestions

You can add recent searches and Query Suggestions using out-of-the-box plugins. Refer to the guides on adding recent searches and Query Suggestions for more detailed explanations.

Creating a recent searches plugin

Use the out-of-the-box createLocalStorageRecentSearchesPlugin function to create a recent searches plugin:

1
2
3
4
5
6
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';

const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
  key: 'RECENT_SEARCH',
  limit: 5,
});

The key can be any string and is required to differentiate search histories if you have multiple autocompletes on one page. The limit defines the maximum number of recent searches to display.

Creating a Query Suggestions plugin

If you don’t have a Query Suggestions index yet, follow the guide on creating a Query Suggestions index. You can also use the demo application credentials and index name provided in this tutorial.

Use the out-of-the-box createQuerySuggestionsPlugin function to create a Query Suggestions plugin. It requires an Algolia search client initialized with an Algolia application ID and API key and an indexName. The indexName is the name of your Query Suggestions index.

1
2
3
4
5
6
7
8
9
10
import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions';

const appId = 'latency';
const apiKey = '6be0576ff61c053d5f9a3225e2a90f76';
const searchClient = algoliasearch(appId, apiKey);

const querySuggestionsPlugin = createQuerySuggestionsPlugin({
  searchClient,
  indexName: 'instant_search_demo_query_suggestions',
});

Coordinating Query Suggestions with other sources

When instantiating your Query Suggestions plugin, you can optionally pass a getSearchParams function to apply Algolia query parameters to the suggestions returned from the plugin. This is particularly useful if you need to coordinate your Query Suggestions with other sections displayed in the autocomplete, like recent searches.

For example, if you’d like to show a combined total of ten search terms (recent searches plus Query Suggestions), you can indicate this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { autocomplete } from '@algolia/autocomplete-js';
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions';

const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
  key: 'RECENT_SEARCH',
  limit: 5,
});

const appId = 'latency';
const apiKey = '6be0576ff61c053d5f9a3225e2a90f76';
const searchClient = algoliasearch(appId, apiKey);

const querySuggestionsPlugin = createQuerySuggestionsPlugin({
  searchClient,
  indexName: 'instant_search_demo_query_suggestions',
  getSearchParams() {
  return recentSearchesPlugin.data.getAlgoliaSearchParams({
    hitsPerPage: 10,
  }),
});

autocomplete({
  // ...
});

This shows up to five recent searches (set by the limit parameter) and up to ten total search terms. If there’s only one recent search in local storage, the autocomplete displays nine Query Suggestions, assuming that there are nine relevant suggestions.

Separating result types

When using sources other than just recent and suggested searches, it’s best to label the different result types with headers. For the createLocalStorageRecentSearchesPlugin and createQuerySuggestionsPlugin plugins, you can use the transformSource option to do this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { autocomplete } from '@algolia/autocomplete-js';
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions';

// ...

const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
  // ...
  transformSource({ source }) {
    return {
      ...source,
      templates: {
        ...source.templates,
        header({ items }) {
          if (items.length === 0) {
            return null;
          }
          return (
            <>
              <span className="aa-SourceHeaderTitle">Recent</span>
              <div className="aa-SourceHeaderLine" />
            </>
          );
        },
      },
    };
  },
});
const querySuggestionsPlugin = createQuerySuggestionsPlugin({
  // ...
  transformSource({ source }) {
    return {
      ...source,
      templates: {
        ...source.templates,
        header({ items }) {
          if (items.length === 0) {
            return null;
          }
          return (
            <>
              <span className="aa-SourceHeaderTitle">Suggestions</span>
              <div className="aa-SourceHeaderLine" />
            </>
          );
        },
      },
    };
  },
});

autocomplete({
  // ...
});

Putting it all together

All that’s left to do is add all your plugins to your autocomplete instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { autocomplete } from '@algolia/autocomplete-js';
import { predefinedItemsPlugin } from './predefinedItemsPlugin';
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions';

const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
  key: 'RECENT_SEARCH',
  limit: 5,
});

const appId = 'latency';
const apiKey = '6be0576ff61c053d5f9a3225e2a90f76';
const searchClient = algoliasearch(appId, apiKey);

const querySuggestionsPlugin = createQuerySuggestionsPlugin({
  searchClient,
  indexName: 'instant_search_demo_query_suggestions',
  getSearchParams() {
    return recentSearchesPlugin.data.getAlgoliaSearchParams({
      hitsPerPage: 10,
    });
  },
});

autocomplete({
  container: '#autocomplete',
  plugins: [
    recentSearchesPlugin,
    querySuggestionsPlugin,
    predefinedItemsPlugin,
  ],
  openOnFocus: true,
});

This creates a basic multi-source autocomplete. Try it out below:

Next steps

This tutorial combined three sources in one autocomplete. Depending on your use case, you might want to add more or different ones than the ones included here. Regardless of what you use for your sections, the method is the same: provide a different a source for each.

You may also choose to style your multi-source autocomplete differently by creating a horizontal layout or further differentiating how to display each source type.

Did you find this page helpful?