Access state outside the lifecycle
On this page
Each InstantSearch widget owns its part of the search state. While this fits the majority of use cases, sometimes you might want to read the state or refine the search outside of the InstantSearch lifecycle. For example, you may want to let users refine on a specific category from a product page, and not just from a refinement list.
InstantSearch.js lets you access the render state of each widget, which you can use to create custom widgets or refine the search outside the InstantSearch.js lifecycle.
The renderState
property is available starting in InstantSearch.js v4.9.
Refining a search from the outside
In InstantSearch, the refinementList
widget controls refinements for a given attribute. You can access its state via the renderState
property.
All examples in this guide assume you’ve included InstantSearch.js in your web page from a CDN. If, instead, you’re using it with a package manager, adjust how you import InstantSearch.js and its widgets for more information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const search = instantsearch({
indexName,
searchClient,
});
search.addWidgets([
// ...
instantsearch.widgets.refinementList({
container: '#brand',
attribute: 'brand',
sortBy: ['isRefined'],
}),
]);
search.renderState[indexName].refinementList.brand; // returns an object containing the render state
The exposed state matches the render options exposed to the connectRefinementList
render options. You can access the state of brand
, but also leverage the
refine
method to programmatically set a refinement.
1
search.renderState[indexName].refinementList.brand.refine('Apple');
This lets you refine on a brand from anywhere in your app, even outside of the InstantSearch lifecycle. For example, you might want to let users search in the related brand whenever they visit a product page.
1
2
3
<button id="apple-search-button">
Search "Apple" products
</button>
1
2
3
4
5
document
.querySelector('#apple-search-button')
.addEventListener('click', () => {
search.renderState[indexName].refinementList.brand.refine('Apple');
});
Accessing the state of other widgets
When refining on a brand, you might end up with no results at all. By default, the hits
widget displays a “No results” message. You can customize it using the connectHits
connector along with the refinementList
render state.
First, remove the default template for empty
hits from the hits
widget so it doesn’t display anything when there are no hits.
1
2
3
4
5
6
7
instantsearch.widgets.hits({
container: '#hits',
templates: {
// ...
empty: ''
}
});
Second, add a button to reproduce the no results situation.
1
2
3
<button id="pear-search-button">
Search "Pear" products
</button>
1
2
3
4
5
document
.querySelector('#pear-search-button')
.addEventListener('click', () => {
search.renderState[indexName].refinementList.brand.refine('Pear');
});
Then, you can add a dedicated custom widget to handle the no results situation.
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
const renderer = ({ hits, widgetParams }) => {
const container = document.querySelector(widgetParams.container);
if (hits.length > 0) {
container.innerHTML = '';
return;
}
const brandState = search.renderState[indexName].refinementList.brand;
const isPearRefined =
brandState.items.filter((item) => item.label === 'Pear' && item.isRefined)
.length > 0;
if (!isPearRefined) {
container.innerHTML = 'No results';
return;
}
container.innerHTML = `
<p>No results for Pear</p>
<button>
Remove "Pear" filter
</button>
`;
container.querySelector('button').addEventListener('click', () => {
search.renderState[indexName].refinementList.brand.refine('Pear');
});
};
const emptyHits = instantsearch.connectors.connectHits(renderer);
search.addWidgets([
emptyHits({
container: '#empty-hits',
})
]);
Even though you’re inside the connectHits
connector, you can access the render state of the brand
refinementList
. It lets you figure out whether “Pear” is among the refined facets, you can display a button to remove it.
The refine
method is a toggle function. When you call it with a value that’s already refined, it removes it from the search state, and vice versa.
If you’re not familiar with customizing widgets with connectors, please check the guide on customizing the complete UI of widgets.
Using the panel
widget
The panel
widget gives you easier access to the render state.
This is the basic implementation of a panel
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const categories = panel({
templates: {
header: 'Categories',
},
})(instantsearch.widgets.hierarchicalMenu);
search.addWidgets([
categories({
container: '#categories',
attributes: [
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
'hierarchicalCategories.lvl2',
'hierarchicalCategories.lvl3',
],
}),
])
The renderState
of hierarchicalMenu
for each attribute are available in the collapsed
and hidden
methods of widget.
1
2
3
4
5
6
7
8
9
10
11
const categories = panel({
templates: {
header: 'Categories',
},
collapsed(options) {
// ...
},
hidden(options) {
// ...
}
})(instantsearch.widgets.hierarchicalMenu);
The provided options
include the same state as you’d find under search.renderState[indexName].hierarchicalMenu['hierarchicalCategories.lvl0']
.
For example, when there are no facets to display, you can hide or collapse the hierarchicalMenu
:
1
2
3
4
5
6
const categories = panel({
// ...
hidden({ items }) {
return items.length === 0;
},
})(instantsearch.widgets.hierarchicalMenu);
You can see a live demo on CodeSandbox.