Guides / Building Search UI / UI & UX patterns

You are currently reading the documentation for InstantSearch.js V4. Read our migration guide to learn how to upgrade from V3 to V4. You can still access the V3 documentation for this page.

Overview

You might want to search across multiple indices at the same time. InstantSearch.js has a built-in solution for this use case: the index widget. Note that widgets scoped under an index widget target the Algolia index that the index widget provides. index widgets that are lower in the widget tree inherit their search parameters from preceding widgets. The index widget is really useful whenever you want to:

  • display hits from different indices
  • share a single searchBox
  • build an autocomplete menu targeting multiple indices

In this guide, you’ll learn how to display results from multiple indices using a single searchBox . Specifically, you’ll learn how to do this for an autocomplete search implementation.

The source code for both examples can be found on our GitHub repository of examples.

Search in multiple indices

In this first example, we use a single searchBox to search into multiple indices. The first hits widget is added at the top level, while the second one is scoped under an index widget. The second hits widget is restricted to display results from the Algolia index referenced by the index widget that precedes it.

In the code below, the first hits widget displays results from the top level instant_search index, while the second hits widget displays results from the instant_search_price_desc index .

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
const search = instantsearch({
  indexName: 'instant_search',
  searchClient,
});

search.addWidgets([
  instantsearch.widgets.searchBox({
    container: '#searchbox',
  }),

  instantsearch.widgets.hits({
    container: '#hits-instant-search',
    templates: {
      item:
        '{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}',
    },
  }),

  instantsearch.widgets
    .index({ indexName: 'instant_search_price_desc' })
    .addWidgets([
      instantsearch.widgets.hits({
        container: '#hits-instant-search-price-desc',
        templates: {
          item:
            '{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}',
        },
      }),
    ]),
]);

search.start();

Any widget can be scoped under an index. In the following example we want to display a different number of hits for our two sets of results. The instant_search index displays 8 results and instant_search_price_desc 16 results. To restrict the number of results per page we use the configure widget. Each widget is scoped under the level that is targeted.

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
const search = instantsearch({
  indexName: 'instant_search',
  searchClient,
});

search.addWidgets([
  instantsearch.widgets.configure({
    hitsPerPage: 8,
  }),

  instantsearch.widgets.searchBox({
    container: '#searchbox',
  }),

  instantsearch.widgets.hits({
    container: '#hits-instant-search',
    templates: {
      item:
        '{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}',
    },
  }),

  instantsearch.widgets
    .index({ indexName: 'instant_search_price_desc' })
    .addWidgets([
      instantsearch.widgets.configure({
        hitsPerPage: 16,
      }),

      instantsearch.widgets.hits({
        container: '#hits-instant-search-price-desc',
        templates: {
          item:
            '{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}',
        },
      }),
    ]),
]);

search.start();

You can find the complete example on our GitHub examples repository.

autocomplete

For this second use case, we are building an autocomplete that searches into multiple indices. The focus of this guide is on search into multiple indices. We do not cover the autocomplete implementation in depth because it has its own guide. The autocomplete is built with Selectize and the autocomplete connector. This guide differs from the previous one in the way hits are appended to 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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: [],
        labelField: 'name',
        valueField: 'name',
        optgroups: indices.map((index, idx) => ({
          $order: idx,
          id: index.indexId,
          name: index.indexId,
        })),
        optgroupField: 'section',
        optgroupLabelField: 'name',
        optgroupValueField: 'id',
        highlight: false,
        onType: refine,
        onBlur() {
          refine(this.getValue());
        },
        onChange: refine,
        score() {
          return function() {
            return 1;
          };
        },
        render: {
          option: hit => `
            <div class="hit">
              ${instantsearch.highlight({ attribute: 'name', hit })}
            </div>
          `,
        },
      });

      return;
    }

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

    select.selectize.clearOptions();
    indices.forEach(index => {
      if (index.hits.length) {
        index.hits.forEach(hit =>
          select.selectize.addOption(
            Object.assign({}, hit, {
              section: index.indexId,
            })
          )
        );
      }
    });

    select.selectize.refreshOptions(select.selectize.isOpen);
  }
);

Once we have our autocomplete component we can use it in our application. The component will have access to both indices: the top level one but the also the one created by the index widget. It also leverages the configure widget to reduce the number of hits.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const search = instantsearch({
  indexName: 'instant_search',
  searchClient,
});

search.addWidgets([
  instantsearch.widgets.index({ indexName: 'instant_search_price_desc' }),

  instantsearch.widgets.configure({
    hitsPerPage: 3,
  }),

  autocomplete({
    container: $('#autocomplete'),
  }),
]);

search.start();

You can find the complete example on GitHub.

Did you find this page helpful?