Create Your Own Widgets
On this page
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 submitting 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 widgetinit
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 Algoliadispose
optional, used to remove the specific configuration which was specified in thegetConfiguration
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() {
// must return an helper configuration object, like the searchParameters
// on the instantsearch constructor
},
init(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(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(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(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(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(opts) {
const helper = opts.helper;
const input = document.querySelector('#searchBox');
input.addEventListener('input', ({currentTarget}) => {
helper.setQuery(currentTarget.value) // update the parameters
.search(); // launch the query
});
}
});
search.addWidget({
render(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();