Concepts / Building Search UI / Autocomplete
May. 10, 2019

Autocomplete

Overview

A common pattern in search is to implement a search box with an autocomplete as a first step of the search experience. InstantSearch.js doesn’t come with a built-in widget for the autocomplete. But we have a connector autocomplete that lets you use an external autocomplete component.

In this guide we will cover the use case where a searchBox displays an autocomplete menu linked to a results page.

We won’t cover the usage of the connector in a multi-index context in this guide. There is a dedicated section about that in the multi-index search guide. You can find the source code of both examples on GitHub.

Results page with autocomplete

This use case is focused on integrating a searchBox with an autocomplete linked to a results page. To implement this we use the library Selectize that provides an API to create an autocomplete menu. Once we have this component, we need to wrap it with our autocomplete connector. The connector exposes three interesting props for this use case: indices and refine. indices contains the list of suggestions and refine is a function that takes a query to retrieve our relevant suggestions.

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
const autocomplete = instantsearch.connectors.connectAutocomplete(
  ({ indices, refine, widgetParams }, isFirstRendering) => {
    const { container } = widgetParams;

    if (isFirstRendering) {
      container.html('<select id="ais-autocomplete"></select>');

      container.find('select').selectize({
        options: [],
        valueField: 'name',
        labelField: 'name',
        searchField: 'name',
        highlight: false,
        onType: refine,
        onChange: refine,
      });

      return;
    }

    const [select] = container.find('select');

    indices.forEach(index => {
      select.selectize.clearOptions();
      index.results.hits.forEach(hit => select.selectize.addOption(hit));
      select.selectize.refreshOptions(select.selectize.isOpen);
    });
  }
);

When we have our autocomplete widget setup we can integrate it in our application. But for that we need to use two instances of InstantSearch. We use two instances because it allows us to configure the number of hits retrieved by the autocomplete differently than the number of results. The huge benefit of it is also to not have the query tied to both instances at the same time. It allows us to clear the suggestions but sill have the query applied on the second instance. Since we have two instances we need a way to sync the query between the two. The first step is to be able to retrieve the value of the autocomplete:

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
const autocomplete = instantsearch.connectors.connectAutocomplete(
  ({ indices, refine, widgetParams }, isFirstRendering) => {
    // We get the onSelectChange callback from the widget params
    const { container, onSelectChange } = widgetParams;

    if (isFirstRendering) {
      container.html('<select id="ais-autocomplete"></select>');

      container.find('select').selectize({
        // ...
        onType: refine,
        onChange(value) {
          // We call the provided callback each time a value is selected
          onSelectChange(value);
          refine(value);
        },
      });

      return;
    }

    // ...
  }
);

const suggestions = instantsearch({
  indexName: 'demo_ecommerce',
  searchClient,
});

suggestions.addWidget(
  autocomplete({
    container: $('#autocomplete'),
    onSelectChange(value) {
      // The callback is triggered each time a value is selected
    },
  })
);

Now that we are able to retrieve the query of the autocomplete, we need a way apply this value to the second instance. To do so we use a reference of the helper that is on our main InstantSearch 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
34
35
36
37
38
39
40
41
const suggestions = instantsearch({
  indexName: 'demo_ecommerce',
  searchClient,
});

suggestions.addWidget(
  autocomplete({
    container: $('#autocomplete'),
    onSelectChange(value) {
      // Now we can trigger the search on our main instance
      // each time the value inside the dropdown is selected
      search.helper.setQuery(value).search();
    },
  })
);

const search = instantsearch({
  indexName: 'demo_ecommerce',
  searchClient,
});

search.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
    templates: {
      item: `
        <div>
          <header class="hit-name">
            {{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}
          </header>
          <p class="hit-description">
            {{#helpers.highlight}}{ "attribute": "description" }{{/helpers.highlight}}
          </p>
        </div>
      `,
    },
  })
);

suggestions.start();
search.start();

That’s it! You can find the complete example on GitHub.

Did you find this page helpful?