Guides Create new widgets

Edit this page

Creating new widgets

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. If you think that your needs are common, don’t hesitate to open an issue or come discuss it on the forum.

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 3 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

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

  • getConfiguration (optional), 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

If we translate this to code, this looks like:

const search = instantsearch();
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
  }
});

A widget is valid as long as it implements at least search 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 parts 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.

const search = instantsearch({
  appId: 'latency',
  apiKey: '6be0576ff61c053d5f9a3225e2a90f76',
  indexName: 'movies',
});

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(opts) {
    const results = opts.results;
    // read the hits from the results and transform them into HTML.
    document.querySelector('#hits').innerHTML = results.hits.map(function(h) {
      return '<p>' + h._highlightResult.title.value + '</p>';
    }).join('');
  }
});

search.start();