Concepts / Building Search UI / Create your own widgets
Jan. 07, 2019

Create your own widgets

You are currently reading the documentation for InstantSearch.js V3. Read our migration guide to learn how to upgrade from V2 to V3. You can still find the V2 documentation here.

Introduction

InstantSearch.js comes with many widgets, by default. If those widgets are not enough, they can be customized using the connectors.

You might find yourself in a situation where both widgets and connectors are not sufficient, that’s why it is possible to create your own widget. Making widgets is the most advanced way of customizing your search experience and it requires a deeper knowledge of InstantSearch.js and Algolia.

You are trying to create your own widget with InstantSearch.js and that’s awesome 🎉. But that also means that you couldn’t find the widgets or built-in options you were looking for. We’d love to hear about your use case as our mission with our InstantSearch libraries is to provide the best out-of-the-box experience. Don’t hesitate to send us a quick message explaining what you were trying to achieve either using the form at the end of that page or directly by submetting a feature request

To be able to start making your own widgets, there are some elements that you need to know:

  • the widget lifecycle
  • how to interact with the search state

There’s a simple example of a custom widget at the end of this guide.

The widget lifecycle and API

InstantSearch.js defines the widget lifecycle of the widgets in 4 steps:

  • the configuration step, during which the initial search configuration is computed
  • the init step, which happens before the first search
  • the render step, which happens after each result from Algolia
  • the dispose step, which happens when you remove the widget or dispose the InstantSearch instance

Thoses steps translate directly into the widget API. Widgets are defined as plain JS objects with 7 methods:

  • getConfiguration optional, used to returns the necessary subpart of the configuration, specific to this widget
  • init optional, used to setup the widget (good place to first setup the initial DOM). Called before the first search.
  • render optional, used to update the widget with the new information from the results. Called after each time results come back from Algolia
  • dispose optional, used to remove the specific configuration which was specified in the getConfiguration method. Called when removing the widget or when InstantSearch disposes itself.
  • getWidgetState optional, used to get the UI state of the widget. This UI state is the object used to create the URL with the routing system.
  • getWidgetSearchParameters optional, used to get the SearchParamters of the widget. This SearchParamters is used to create the correct request from an URL with the routing system.

If we translate this to code, this looks like:

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
search.addWidget({
  getConfiguration: function() {
    // must return an helper configuration object, like the searchParameters
    // on the instantsearch constructor
  },
  init: function(initOptions) {
    // initOptions contains three keys:
    //   - helper: to modify the search state and propagate the user interaction
    //   - state: which is the state of the search at this point
    //   - templatesConfig: the configuration of the templates
  },
  render: function(renderOptions) {
    // renderOptions contains four keys:
    //   - results: the results from the last request
    //   - helper: to modify the search state and propagate the user interaction
    //   - state: the state at this point
    //   - createURL: if the url sync is active, will make it possible to create new URLs
  },
  dispose: function(disposeOptions) {
    // disposeOptions contains one key:
    //   - state: the state at this point to
    //
    // The dispose method should return the next state of the search,
    // if it has been modified.
  },
  getWidgetState: function(uiState, widgetStateOptions) {
    // widgetStateOptions contains two keys:
    //   - searchParameters: to compute the next uiState
    //   - helper: to get information about the state of the search
    //
    // The function must return the next uiState
  },
  getWidgetSearchParameters: function(searchParameters, widgetSearchParametersOptions) {
    // widgetSearchParametersOptions contains one key:
    //   - uiState: to compute the next SearchParameters
    //
    // The function must return the next SearchParameters
  }
});

A widget is valid as long as it implements at least render or init.

Interacting with the Search State

The previous custom widget API boilerplate is the reading part of the widgets. To be able to transform user interaction into search parameters we need to be able to modify the state.

The whole search state is held by an instance of the JS Helper in InstantSearch.js. This instance of the helper is accessible at the init and render phases.

The helper is used to change the parameters of the search. It provides methods to change each part of it. After changing the parameters, you should use the search method to trigger a new search. This search is then handled by Algolia and when the results come back, InstantSearch will dispatch the new results to all the widgets.

Mastering the creation of new widgets is closely linked to using the JS Helper, that’s why we recommend you read about its concepts and have a look at the getting started. You can also read more about the features offered by this library in the reference API.

Full custom widget example

To give you an idea of the power of this API, let’s have a look at a minimal implementation of a search UI with a searchbox and hits.

In this example, the widgets are not reusable and will assume that the DOM is already set up. You can see the example live on jsFiddle.

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 searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

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

search.addWidget({
  init: function(opts) {
    const helper = opts.helper;
    const input = document.querySelector('#searchBox');
    input.addEventListener('input', function(e) {
      helper.setQuery(e.currentTarget.value) // update the parameters
            .search(); // launch the query
    });
  }
});

search.addWidget({
  render: function(options) {
    const results = options.results;
    // read the hits from the results and transform them into HTML.
    document.querySelector('#hits').innerHTML = results.hits
      .map(
        hit => `<p>${instantsearch.highlight({ attribute: 'title', hit })}</p>`
      )
      .join('');
  },
});

search.start();

Did you find this page helpful?