Upgrade templates
Starting from InstantSearch.js v4.46.0, string-based and Hogan.js templates are deprecated.
If you’re using Hogan.js templates or HTML strings using string-based templates, you can replace them either with safe HTML strings using the provided html
tagged template, or JSX templates.
These new templating options are safer against cross-site scripting (XSS) attacks, perform better, and are more accessible, because they reuse and patch the existing DOM instead of replacing it entirely at each render.
Interpolation
You can interpolate dynamic values using template literals placeholders.
All available dynamic values are exposed within the scope of the template function.
1
2
3
4
5
6
7
8
| toggleRefinement({
// ...
templates: {
labelText({ count }, { html }) {
return html`Free shipping (${count})`;
},
},
});
|
1
2
3
4
5
6
7
8
| // Don't do this, use `html` instead
toggleRefinement({
// …
templates: {
labelText: 'Free shipping ({{ count }})',
},
});
|
1
2
3
4
5
6
7
8
9
10
| // Don't do this, use `html` instead
toggleRefinement({
// ...
templates: {
labelText({ count }) {
return `Free shipping (${count})`;
},
},
});
|
Highlighting and snippeting
In both hits
and infiniteHits
widgets, templates expose a set of built-in components to handle highlighting and snippeting.
1
2
3
4
5
6
7
8
9
10
11
| hits({
// ...
templates: {
item(hit, { html, components }) {
return html`
<h2>${components.Highlight({ hit, attribute: 'name' })}</h2>
<p>${components.Snippet({ hit, attribute: 'description' })}</p>
`;
},
},
});
|
1
2
3
4
5
6
7
8
9
10
11
| // Don't do this, use `html` instead
hits({
// ...
templates: {
item: `
<h2>{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}</h2>
<p>{{#helpers.snippet}}{ "attribute": "description" }{{/helpers.snippet}}</p>
`,
},
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| // Don't do this, use `html` instead
hits({
// ...
templates: {
item(hit) {
return `
<h2>${instantsearch.highlight({ attribute: 'name', hit })}</h2>
<p>${instantsearch.snippet({ attribute: 'description', hit })}</p>
`;
},
},
});
|
Loops
You can use plain JavaScript to build dynamic templates.
For example, you can use Array.map
to loop over an array and display a list.
1
2
3
4
5
6
7
8
9
10
| ratingMenu({
// …
templates: {
item({ url, stars }, { html }) {
return html`<a href="${url}">
${stars.map((_, index) => html`<svg key="${index}"><!-- … --></svg>`)}
</a>`;
},
},
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Don't do this, use `html` instead
ratingMenu({
// …
templates: {
item: `
<a href="{{url}}">
{{#stars}}
<svg><!-- … --></svg>
{{/stars}}
</a>
`,
},
});
|
1
2
3
4
5
6
7
8
9
10
11
12
| // Don't do this, use `html` instead
ratingMenu({
// …
templates: {
item({ url, stars }) {
return `<a href="${url}">
${stars.map((_, index) => `<svg><!-- … --></svg>`)}
</a>`;
},
},
});
|
Passing a unique key
attribute is helpful when mapping over items. It helps the virtual DOM keep track of each element when they change, and update the UI efficiently.
Conditional rendering
To conditionally render a part of your UI, you can use a short-circuit operator or a ternary.
Since templates are functions, you can also use early returns to provide alternative templates.
1
2
3
4
5
6
7
8
9
10
11
12
| stats({
// …
templates: {
text({ nbHits }, { html }) {
if (nbHits === 0) {
return html`<p>No results.</p>`;
}
return html`<p>${nbHits} result${nbHits > 1 ? 's' : ''} found.</p>`;
},
},
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| // Don't do this, use `html` instead
stats({
// …
templates: {
text: `
{{#hasNoResults}}<p>No results.</p>{{/hasNoResults}}
{{^hasNoResults}}
<p>{{nbHits}} result{{#hasManyResults}}results{{/hasManyResults}} found.</p>
{{/hasNoResults}}
`,
},
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Don't do this, use `html` instead
stats({
// …
templates: {
text({ nbHits }) {
if (nbHits === 0) {
return `<p>No results.</p>`;
}
return `<p>${nbHits} result${nbHits > 1 ? 's' : ''} found.</p>`;
},
},
});
|
Upgrade event tracking
Starting from v4.55.0, InstantSearch simplifies the event tracking process with the insights
option.
You no longer need to install the search-insights
library or set up the insights
middleware yourself.
Here are some benefits when using the insights
option:
- It better handles the
userToken
. Once you set it, all the search and event tracking calls include the token.
- It automatically sends default events from built-in widgets such as
refinementList
, menu
, etc. You can also change the event payloads, or remove them altogether.
- It lets you send custom events from your custom widgets.
- It simplifies forwarding events to third-party trackers.
If you’ve been tracking events directly with search-insights
or with the insights
middleware, you should:
- Upgrade InstantSearch.js to v4.55.0 or greater
- Migrate from using the
insights
middleware to the insights
option
- Either update or remove the
search-insights
library
Use the insights
option
Starting from v4.55.0, InstantSearch lets you enable event tracking with the insights
option. You no longer need to set up the insights
middleware yourself.
1
2
3
4
| const search = instantsearch({
// …
insights: true,
});
|
If you had already set up the insights
middleware in your code, you can now remove it and move its configuration to the insights
option.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| - import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares';
const search = instantsearch({
insights: {
+ insightsInitParams: {
+ useCookie: false,
+ },
},
});
- search.use(
- createInsightsMiddleware({
- insightsInitParams: {
- useCookie: false,
- },
- })
- );
|
Update search-insights
Starting from v4.55.0, InstantSearch can load search-insights
for you so the insightsClient
option is no longer required.
If you prefer loading it yourself, make sure to update search-insights
to at least v2.4.0 and forward the reference to insights
.
If you’re using the UMD bundle with a <script>
tag, make sure to update the full code snippet (not just the version):
1
2
3
4
5
6
7
8
| <script>
var ALGOLIA_INSIGHTS_SRC = "https://cdn.jsdelivr.net/npm/search-insights@2.17.3/dist/search-insights.min.js";
!function(e,a,t,n,s,i,c){e.AlgoliaAnalyticsObject=s,e[s]=e[s]||function(){
(e[s].queue=e[s].queue||[]).push(arguments)},e[s].version=(n.match(/@([^\/]+)\/?/) || [])[1],i=a.createElement(t),c=a.getElementsByTagName(t)[0],
i.async=1,i.src=n,c.parentNode.insertBefore(i,c)
}(window,document,"script",ALGOLIA_INSIGHTS_SRC,"aa");
</script>
|
If you’re using a package manager, you can upgrade it to the latest version.
1
| npm install search-insights
|
Now you can pass the reference to the insights
option.
1
2
3
4
5
| const search = instantsearch({
insights: {
insightsClient: window.aa,
},
});
|
1
2
3
4
5
6
7
| import aa from 'search-insights';
const search = instantsearch({
insights: {
insightsClient: aa,
},
});
|
Otherwise, you can remove it and let InstantSearch handle it for you.
Remove search-insights
Starting from v4.55.0, InstantSearch loads search-insights
for you from jsDelivr if not detected in the page. If you’ve installed search-insights
, you can now remove it.
If you’re using the UMD bundle with a <script>
tag, you can remove the snippet in any page that uses InstantSearch:
1
2
3
4
5
6
7
8
| - <script>
- var ALGOLIA_INSIGHTS_SRC = "https://cdn.jsdelivr.net/npm/search-insights@2.17.3/dist/search-insights.min.js";
-
- !function(e,a,t,n,s,v,i,c){e.AlgoliaAnalyticsObject=s,e[s]=e[s]||function(){
- (e[s].queue=e[s].queue||[]).push(arguments)},e[s].version=(n.match(/@([^\/]+)\/?/) || [])[1],i=a.createElement(t),c=a.- getElementsByTagName(t)[0],
- i.async=1,i.src=n,c.parentNode.insertBefore(i,c)
- }(window,document,"script",ALGOLIA_INSIGHTS_SRC,"aa");
- </script>
|
If you’re using a package manager, you can remove it from your dependencies.
1
| npm uninstall search-insights
|
Then you can remove the reference to search-insights
from your code:
1
2
3
4
5
| const search = instantsearch({
insights: {
- insightsClient: window.aa,
},
});
|
1
2
3
4
5
6
7
8
9
| - const aa = require('search-insights');
// or
- import aa from 'search-insights';
const search = instantsearch({
insights: {
- insightsClient: aa,
},
});
|
InstantSearch loads search-insights
from the jsDelivr CDN, which requires that your site or app allows script execution from foreign resources. Check the security best practices for recommendations.
No longer needed configuration
clickAnalytics: true
1
2
3
| instantsearch.widgets.configure({
clickAnalytics: true,
}),
|
This isn’t needed anymore because it’s automatically added by the middleware.
getInsightsAnonymousUserToken()
1
2
3
| instantsearch.widgets.configure({
userToken: instantsearch.getInsightsAnonymousUserToken()
});
|
You no longer need this. Once you integrate the insights
middleware, it initially sets the anonymous userToken
. As soon as you set another userToken
, it automatically syncs it between search and analytics calls.
insightsClient
at InstantSearch
1
2
3
4
5
| const search = instantsearch({
searchClient,
indexName: 'INDEX_NAME',
insightsClient: window.aa,
});
|
Now that you are using the Insights
middleware, you don’t need to pass aa
to instantsearch()
anymore.
hits
and infiniteHits
template
You might have templates in your hits
or infiniteHits
widgets that look like the following:
1
2
3
4
5
6
7
8
| <button
${instantsearch.insights('convertedObjectIDsAfterSearch', {
eventName: 'Product Added',
objectIDs: [hit.objectID]
})}
>
Add to cart
</button>
|
These are now deprecated. Instead, you can use the sendEvent
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| instantsearch.widgets.hits({
container: '#hits',
templates: {
item(hit, { html, sendEvent, components }) {
return html`
<article>
<h3>${components.Highlight({ attribute: 'name', hit })}</h3>
<button
onClick=${() => sendEvent('conversion', hit, 'Product Added')}
>
Add to cart
<!-- clicking this button sends a `conversion` event -->
</button>
</article>
`;
}
}
})
|
The analytics
widget has been deprecated in favor of the insights
middleware.
When the built-in widgets trigger default events, you can intercept them using the insights
middleware’s onEvent
hook and send them to third-party trackers.
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
| import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares';
// Or, use `instantsearch.middlewares.createInsightsMiddleware()`
const indexName = '<your-index-name>';
const search = instantsearch({
indexName,
// ...
});
const insightsMiddleware = createInsightsMiddleware({
insightsClient: window.aa,
onEvent: (event, aa) => {
const { insightsMethod, payload, widgetType, eventType } = event;
// Send the event to Algolia
aa(insightsMethod, payload);
// Send click events on Hits to a third-party tracker
if (widgetType === 'ais.hits' && eventType === 'click') {
thirdPartyTracker.send('Product Clicked', payload);
}
}
});
search.use(insightsMiddleware);
|
If you want to send events every time the query or refinements change, you can add a custom middleware to listen to uiState
changes with the onStateChange
function.
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 indexName = '<your-index-name>';
const search = instantsearch({
indexName,
// ...
});
const analyticsMiddleware = () => {
return {
onStateChange({ uiState }) {
// Google Analytics (Universal Analytics)
window.ga('set', 'page', window.location.pathname + window.location.search);
window.ga('send', 'pageView');
// Google Analytics V4
gtag('event', 'page_view', {
page_location: window.location.pathname + window.location.search,
});
// Google Tag Manager (GTM)
// You can use `uiState` to make payloads for third-party trackers.
dataLayer.push({
'event': 'search',
'Search Query': uiState[indexName].query,
'Brand': uiState[indexName].refinementList['brand'].join(','),
});
},
subscribe() {},
unsubscribe() {},
}
}
search.use(analyticsMiddleware);
|
You can also use external libraries like lodash.debounce
to debounce the events you send.
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
| import debounce from 'lodash.debounce';
const indexName = '<your-index-name>';
const search = instantsearch({
indexName,
// ...
});
const sendEventDebounced = debounce((uiState) => {
// Google Analytics
window.ga('set', 'page', window.location.pathname + window.location.search);
window.ga('send', 'pageView');
// Google Analytics V4
gtag('event', 'page_view', {
page_location: window.location.pathname + window.location.search,
});
// Google Tag Manager (GTM)
dataLayer.push({
'event': 'search',
'Search Query': uiState[indexName].query,
'Brand': uiState[indexName].refinementList['brand'].join(','),
});
}, 3000);
const analyticsMiddleware = () => {
return {
onStateChange({ uiState }) {
sendEventDebounced(uiState);
},
subscribe() {},
unsubscribe() {},
}
}
search.use(analyticsMiddleware);
|
Upgrade to Recommend
The relatedProducts
widget is now available instead of the previous experimental widget EXPERIMENTAL_relatedItems
. To migrate, you change the widget name and update the props:
1
2
3
4
5
6
7
8
9
10
11
12
13
| - import { EXPERIMENTAL_relatedItems } from 'instantsearch.js/es/widgets';
+ import { relatedProducts } from 'instantsearch.js/es/widgets';
search.addWidgets([
- EXPERIMENTAL_relatedItems({
- hit: { objectID: '123' },
- }),
- hits({ container: '#related-products' }),
+ relatedProducts({
+ container: '#related-products',
+ objectIDs: ['123'],
+ }),
]);
|
Upgrade from v3 to v4
The latest version of InstantSearch mostly focuses on federated search and bundle size. This required some changes that might impact your app.
Federated search (multi-index)
If you were already using federated search (for example by synchronizing two instantsearch
instances via the searchFunction
), the implementation is now simpler. Instead, try the new index
widget, to which you can attach more widgets. For example:
1
2
3
4
5
6
7
8
9
10
| const search = instantsearch({ indexName: 'primary', /* ... */ });
search.addWidgets([
searchBox(),
hits(),
index({ indexName: 'secondary' }).addWidgets([
searchBox(),
hits(),
])
]);
|
Routing
Even if you aren’t using multi-index search, the way in which uiState
is stored has changed.
It used to look like this:
1
2
3
4
| {
"query": "value",
"page": 5
}
|
It now looks like this:
1
2
3
4
5
6
| {
"indexName": {
"query": "value",
"page": 5
}
}
|
If you are using the default state mapping (simpleStateMapping
) with the current version, you can replace it with singleIndexStateMapping('yourIndexName')
. You have to change the code as followed:
1
2
3
4
5
6
7
8
| instantsearch({
indexName: 'myIndex',
routing: {
- stateMapping: instantsearch.stateMappings.simple(),
+ stateMapping: instantsearch.stateMappings.singleIndex('myIndex'),
}
// ...
});
|
If you are using a custom state mapping, you have to loop over the outer level of the index
widget and add this extra level to the routeToState
. You can check the source for a reference on how to implement this.
For example, a stateMapping
that maps a few properties would change like this:
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
| // Before
const stateMapping = {
stateToRoute(uiState) {
return {
query: uiState.query,
page: uiState.page,
// ...
};
},
routeToState(routeState) {
return {
query: routeState.query,
page: routeState.page,
// ...
};
},
};
// After
const stateMapping = {
stateToRoute(uiState) {
const indexUiState = uiState[indexName];
return {
query: indexUiState.query,
page: indexUiState.page,
// ...
};
},
routeToState(routeState) {
return {
[indexName]: {
query: routeState.query,
page: routeState.page,
// ...
},
};
},
};
|
The configure
widget is now included in uiState
.
If you want to exclude it from the URL you can use the default stateMapping
s or exclude it in your custom state mapping.
A good reason to exclude the configure widget from the UI state is to prevent users from adding any search parameters.
You must exclude this widget in both the stateToRoute
, to keep it from appearing in the URL and routeToState
, so that the URL doesn’t apply to the state.
Check the stateMapping
source code for implementation details.
Algolia search helper
This release includes version 3 of the algoliasearch-helper
package. If you’re using the built-in widgets or connectors, nothing changes for you.
This version of algoliasearch-helper
no longer includes Lodash, which reduces its bundle size (from 27.5 KB to 9.1 KB Gzipped).
If you’re using any methods from the helper
, searchParameters
or searchResults
, refer to the package’s change log.
searchParameters
The option searchParameters
was removed from the instantsearch
widget. You can replace it with the configure
widget, like this:
1
2
3
4
5
6
7
8
9
10
11
12
| const search = instantsearch({
// ...
- searchParameters: {
- hitsPerPage: 5,
- },
});
+ search.addWidgets([
+ instantsearch.widgets.configure({
+ hitsPerPage: 5,
+ }),
+ ]);
|
You can now add initialUiState
to your InstantSearch
widget. This overwrites specific searchParameters
that are otherwise set during widget instantiation. initialUiState
is only taken into account if a widget owning that “state” is mounted. A warning appears in development mode explaining which widget needs to be added for the UI state to have an effect.
An example is a refinementList
widget:
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
| const search = instantsearch({
// ...
- searchParameters: {
- disjunctiveFacets: ['brand'],
- disjunctiveFacetsRefinements: {
- brand: ['Apple'],
- },
- },
+ initialUiState: {
+ refinementList: {
+ brand: ['Apple'],
+ },
+ },
});
search.addWidgets([
refinementList({ attribute: 'brand' }),
]);
// or when there should be no display for this widget:
const virtualRefinementList = connectRefinementList(() => null);
search.addWidgets([
virtualRefinementList({ attribute: 'brand' }),
]);
|
The search.addWidget
function is deprecated in favor of search.addWidgets
, which accepts an array of widgets.
Using arrays makes it clearer to see the structure of nested widgets. search.removeWidget
is deprecated in favor of search.removeWidgets
, which also accepts an array of widgets.
The widget which used to ship with Places.js is no longer compatible with InstantSearch. It has been replaced with one that ships directly with InstantSearch.js.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import algoliaPlaces from 'places.js';
import { places } from 'instantsearch.js/es/widgets';
const search = instantsearch({
/* ... */
});
search.addWidgets([
places({
placesReference: algoliaPlaces,
defaultPosition: ['37.7793', '-122.419'], // optional
container: '#places',
}),
]);
|
The widget parameters are:
placesReference
: the places.js
library.
defaultPosition
: the coordinates to use if users don’t choose a location.
container
: the DOM element to mount the input into.
...other
: places.js
options
Because version 3 of the algoliasearch-helper
package is used, you have to replace the getConfiguration
lifecycle with getWidgetSearchParameters
and getWidgetState
.
This also means that your custom widget takes part in the routing. You can exclude it from the URL via stateMapping
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| const widget = {
getConfiguration(searchParams) {
return {
disjunctiveFacets: ['myAttribute'],
};
},
getWidgetSearchParameters(searchParameters, { uiState }) {
return searchParameters
.addDisjunctiveFacetRefinement(
'myAttribute',
uiState.myWidgetName.myAttribute
);
},
getWidgetState(uiState, { searchParameters }) {
return {
...uiState,
myWidgetName: {
myAttribute: searchParameters.getDisjunctiveRefinements('myAttribute')
}
};
}
};
|
Becomes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| const widget = {
getWidgetSearchParameters(searchParameters, { uiState }) {
return searchParameters
.addDisjunctiveFacet('myAttribute')
.addDisjunctiveFacetRefinement(
'myAttribute',
uiState.myWidgetName.myAttribute
);
},
getWidgetState(uiState, { searchParameters }) {
return {
...uiState,
myWidgetName: {
myAttribute: searchParameters.getDisjunctiveRefinements('myAttribute')
}
};
}
};
|
connectAutoComplete
The indices
option was removed in favor of index
widgets (see the federated search section). For example, the following code:
1
2
3
4
5
6
7
8
9
| const autocomplete = connectAutocomplete(() => {/* ... */});
search.addWidget(
autocomplete({
indices: [{
name: 'additional'
}]
})
);
|
Should be replaced with this:
1
2
3
4
5
6
| const autocomplete = connectAutocomplete(() => {/* ... */});
search.addWidgets([
index({ indexName: 'additional' }),
autocomplete()
]);
|
onHistoryChange
The onHistoryChange
function is removed. To subscribe to changes in the URL, create a custom router
and attach to the write
hook. For example:
1
2
3
4
5
6
| const router = historyRouter()
const originalWrite = router.write.bind(router)
router.write = state => {
console.log('listen to route state here');
originalWrite(state)
}
|
router
The dispose
function on the router
interface is now required and its signature is updated (see the example below). This change only impacts you if you use a custom router.
1
2
3
4
5
6
7
| interface Router {
// ...
// Before
dispose?({ helper, state }: { helper: AlgoliaSearchHelper, state: SearchParameters }): SearchParameters | void
// After
dispose(): void
}
|
The noRefinementRoot
CSS class gets added once there are no more possible refinements. It no longer gets applied on the first page, which was the wrong behavior.
Upgrade from v2 to v3
InstantSearch v3 introduces some breaking changes in the widgets’ naming, options, and markup.
What’s new
Search from your own search client
Since InstantSearch.js 2.8.0, it’s possible to search from your own backend using the searchClient
option.
This option is now the way to plug InstantSearch to either Algolia’s official JavaScript client or to yours. This means that the official search client isn’t bundled in InstantSearch, allowing for more flexibility. This results in a smaller bundle size when you’re not searching directly from the frontend, or that you use multiple instances of the search client.
Predictable DOM structure
InstantSearch.js v3 becomes more predictable by generating a DOM that follows Algolia’s InstantSearch.css specification. All flavors are now aligned, which makes it easier to migrate from one library flavor to another. The options and the DOM output are similar whether you use React InstantSearch, Vue InstantSearch, Angular InstantSearch or InstantSearch.js.
Optimize InstantSearch based on your environment
The new bundle comes in two forms:
- Development bundle. This is a heavier bundle that helps developers better debug in the development phase of an InstantSearch app. It’s more verbose and gives clues and warnings about the library usage.
- Production bundle. This is a lighter bundle that doesn’t include any development warnings. Use this when deploying your app in a production environment.
Imports
UMD (Universal Module Definition)
New naming has been introduced for the UMD imports. This makes it clearer which InstantSearch.js bundle to use either in development or in production. The production bundle will get lighter over time as it won’t include the runtime warnings and documentation.
Before
1
2
3
4
5
| dist
├── instantsearch.js
├── instantsearch.js.map
├── instantsearch.min.js
└── instantsearch.min.js.map
|
After
1
2
3
4
5
| dist
├── instantsearch.development.js
├── instantsearch.development.js.map
├── instantsearch.production.min.js
└── instantsearch.production.min.js.map
|
CommonJS (CJS)
Before
1
| const instantsearch = require('instantsearch.js');
|
After
1
| const instantsearch = require('instantsearch.js').default;
|
ES (ECMAScript)
No changes.
InstantSearch
The variables appId
and apiKey
are replaced by searchClient
.
Earlier usage
- Import
InstantSearch.js
- Initialize InstantSearch
1
2
3
4
5
6
7
| const search = instantsearch({
indexName: 'indexName',
appId: 'appId',
apiKey: 'apiKey',
});
search.start();
|
New usage
- Import
algoliasearch
(prefer the lite version for search only)
- Import
InstantSearch.js
- Initialize InstantSearch with the
searchClient
option
1
2
3
4
5
6
| const search = instantsearch({
indexName: 'indexName',
searchClient: algoliasearch('appId', 'apiKey'),
});
search.start();
|
Since InstantSearch.js first public release, you can customize the values used in the widgets. This method was letting you map 1-1 the values with other values. With React InstantSearch, a slightly different API was implemented that lets you map over the list of values and to change their content.
Earlier usage
1
2
3
4
5
6
7
8
9
10
11
12
| search.addWidgets([
instantsearch.widget.refinementList({
container: '#facet',
attributeName: 'facet',
transformData: {
item: data => {
data.count = 0;
return data;
},
},
})]
);
|
New usage
1
2
3
4
5
6
7
8
9
10
11
| search.addWidgets([
instantsearch.widget.refinementList({
container: '#facet',
attribute: 'facet',
transformItems: items =>
items.map(item => ({
...item,
count: 0,
})),
})
]);
|
instantsearch.highlight
and instantsearch.snippet
Highlighting is a powerful tool for showing users why results match their queries. Previously, InstantSearch used internal mechanisms or Hogan.js to implement highlighting within widget templates. Algolia now provides html
tagged templates.
Earlier usage
1
2
3
4
5
6
7
8
| search.addWidgets([
instantsearch.widget.hits({
container: '#hits',
templates: {
item: '{{{ _highlightResult.name.value }}}',
},
})
]);
|
1
2
3
4
5
6
7
8
| search.addWidgets([
instantsearch.widget.hits({
container: '#hits',
templates: {
item: '{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}',
},
})
]);
|
New usage
1
2
3
4
5
6
7
8
| search.addWidgets([
instantsearch.widget.hits({
container: '#hits',
templates: {
item: (hit, { components }) => components.Highlight({ attribute: 'name', hit }),
},
})
]);
|
urlSync
is dropped
If you were previously using the urlSync
option, you should now migrate to the new routing
feature.
Here are the elements you need to migrate:
urlSync: true
becomes routing: true
threshold
becomes routing: { router: instantsearch.routers.history({ writeDelay: 400 }) }
mapping
and trackedParameters
are replaced with stateMapping
. Read “User friendly URLs” to know how to configure it
useHash
is removed but can be achieved using an advanced configuration of the history router
getHistoryState
is removed but can be achieved using an advanced configuration of the history router
collapsible
is dropped
collapsible
is replaced by the collapsed
option in the panel
widget.
autoHideContainer
is dropped
autoHideContainer
is replaced by the hidden
option in the panel
widget.
createAlgoliaClient
is dropped
createAlgoliaClient
is replaced by searchClient
.
createQueryString
is dropped
URL synchronization is done via Routing alone now.
Breadcrumb
CSS classes
Before |
After |
ais-breadcrumb |
ais-Breadcrumb |
|
ais-Breadcrumb--noRefinement |
ais-breadcrumb |
ais-Breadcrumb-list |
ais-breadcrumb--separator |
ais-Breadcrumb-separator |
ais-breadcrumb--label |
ais-Breadcrumb-link |
ais-breadcrumb--disabledLabel |
|
|
ais-Breadcrumb-item |
|
ais-Breadcrumb-item--selected |
Markup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <div class="ais-Breadcrumb">
<ul class="ais-Breadcrumb-list">
<li class="ais-Breadcrumb-item">
<a class="ais-Breadcrumb-link" href="#">Home</a>
</li>
<li class="ais-Breadcrumb-item">
<span class="ais-Breadcrumb-separator" aria-hidden="true">></span>
<a class="ais-Breadcrumb-link" href="#">Cooking</a>
</li>
<li class="ais-Breadcrumb-item ais-Breadcrumb-item--selected">
<span class="ais-Breadcrumb-separator" aria-hidden="true">></span>
Kitchen textiles
</li>
</ul>
</div>
|
Options
Before |
After |
excludeAttributes |
excludedAttributes |
CSS classes
Before |
After |
ais-clear-all |
ais-ClearRefinements |
ais-clear-all--body |
|
ais-clear-all--link |
|
|
ais-ClearRefinements-button |
|
ais-ClearRefinements-button--disabled |
Markup
1
2
3
4
5
| <div class="ais-ClearRefinements">
<button class="ais-ClearRefinements-button">
Clear refinements
</button>
</div>
|
Options
Before |
After |
attributes |
includedAttributes |
|
excludedAttributes |
onlyListedAttributes |
use includedAttributes |
clearsQuery |
use excludedAttributes |
clearAll |
use the clearRefinements widget |
clearsQuery
can be replaced by excludedAtributes: []
.
CSS classes
Before |
After |
ais-current-refined-values |
ais-CurrentRefinements |
ais-current-refined-values--list |
ais-CurrentRefinements-list |
ais-current-refined-values--item |
ais-CurrentRefinements-item |
ais-current-refined-values--link |
ais-CurrentRefinements-label |
ais-current-refined-values--count |
|
|
ais-CurrentRefinements-category |
|
ais-CurrentRefinements-categoryLabel |
|
ais-CurrentRefinements-delete |
|
ais-CurrentRefinements-query |
ais-current-refined-values--clear-all |
|
Markup
Default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <div class="ais-CurrentRefinements">
<ul class="ais-CurrentRefinements-list">
<li class="ais-CurrentRefinements-item">
<span class="ais-CurrentRefinements-label">
Category:
</span>
<span class="ais-CurrentRefinements-category">
<span class="ais-CurrentRefinements-categoryLabel">Movies & TV Shows</span>
<button class="ais-CurrentRefinements-delete">✕</button>
</span>
<span class="ais-CurrentRefinements-category">
<span class="ais-CurrentRefinements-categoryLabel">Others</span>
<button class="ais-CurrentRefinements-delete">✕</button>
</span>
</li>
</ul>
</div>
|
With includesQuery
and a query
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
| <div class="ais-CurrentRefinements">
<ul class="ais-CurrentRefinements-list">
<li class="ais-CurrentRefinements-item">
<span class="ais-CurrentRefinements-label">
Category:
</span>
<span class="ais-CurrentRefinements-category">
<span class="ais-CurrentRefinements-categoryLabel">Movies & TV Shows</span>
<button class="ais-CurrentRefinements-delete">✕</button>
</span>
<span class="ais-CurrentRefinements-category">
<span class="ais-CurrentRefinements-categoryLabel">Others</span>
<button class="ais-CurrentRefinements-delete">✕</button>
</span>
</li>
<li class="ais-CurrentRefinements-item">
<span class="ais-CurrentRefinements-label">
Query:
</span>
<span class="ais-CurrentRefinements-category">
<span class="ais-CurrentRefinements-categoryLabel">
<q>My query</q>
</span>
<button class="ais-CurrentRefinements-delete">✕</button>
</span>
</li>
</ul>
</div>
|
Geographical search
Options
Before |
After |
customHTMLMarker.template |
templates.HTMLMarker |
paddingBoundingBox |
Removed |
enableGeolocationWithIP |
Removed - use the Configure widget instead (see below) |
position |
Removed - use the Configure widget instead (see below) |
radius |
Removed - use the Configure widget instead (see below) |
precision |
Removed - use the Configure widget instead (see below) |
Since paddingBoundingBox
conflicted with the routing
option it was removed to support URLSync for the GeoSearch widget.
enableGeolocationWithIP
Before:
1
2
3
4
5
| instantsearch.widgets.geoSearch({
googleReference: window.google,
enableGeolocationWithIP: true,
container,
});
|
After:
1
2
3
4
5
6
7
8
| instantsearch.widgets.configure({
aroundLatLngViaIP: true,
});
instantsearch.widgets.geoSearch({
googleReference: window.google,
container,
});
|
position
Before:
1
2
3
4
5
| instantsearch.widgets.geoSearch({
googleReference: window.google,
position: { lat: 40.71, lng: -74.01 },
container,
});
|
After:
1
2
3
4
5
6
7
8
| instantsearch.widgets.configure({
aroundLatLng: '40.71, -74.01',
});
instantsearch.widgets.geoSearch({
googleReference: window.google,
container,
});
|
radius
Before:
1
2
3
4
5
| instantsearch.widgets.geoSearch({
googleReference: window.google,
radius: 1000,
container,
});
|
After:
1
2
3
4
5
6
7
8
| instantsearch.widgets.configure({
aroundRadius: 1000,
});
instantsearch.widgets.geoSearch({
googleReference: window.google,
container,
});
|
precision
Before:
1
2
3
4
5
| instantsearch.widgets.geoSearch({
googleReference: window.google,
precision: 1000,
container,
});
|
After:
1
2
3
4
5
6
7
8
| instantsearch.widgets.configure({
aroundPrecision: 1000,
});
instantsearch.widgets.geoSearch({
googleReference: window.google,
container,
});
|
CSS classes
Before |
After |
ais-geo-search |
ais-GeoSearch |
ais-geo-search--map |
ais-GeoSearch-map |
ais-geo-search--controls |
ais-GeoSearch-tree |
ais-geo-search--control |
ais-GeoSearch-control |
ais-geo-search--toggle-label |
ais-GeoSearch-label |
ais-geo-search--toggle-label-active |
ais-GeoSearch-label--selected |
ais-geo-search--toggle-input |
ais-GeoSearch-input |
ais-geo-search--redo |
ais-GeoSearch-redo |
|
ais-GeoSearch-redo--disabled |
ais-geo-search--clear |
ais-GeoSearch-reset |
Markup
With the control element:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <div class="ais-GeoSearch">
<div class="ais-GeoSearch-map">
<!-- Map element here -->
</div>
<div class="ais-GeoSearch-tree">
<div class="ais-GeoSearch-control">
<label class="ais-GeoSearch-label">
<input class="ais-GeoSearch-input" type="checkbox">
Search as I move the map
</label>
</div>
<button class="ais-GeoSearch-reset">
Clear the map refinement
</button>
</div>
</div>
|
With the redo button:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <div class="ais-GeoSearch">
<div class="ais-GeoSearch-map">
<!-- Map element here -->
</div>
<div class="ais-GeoSearch-tree">
<div class="ais-GeoSearch-control">
<button class="ais-GeoSearch-redo">
Redo search here
</button>
</div>
<button class="ais-GeoSearch-reset">
Clear the map refinement
</button>
</div>
</div>
|
Hits
Options
Before |
After |
escapeHits |
escapeHTML |
showMoreLabel |
loadMoreLabel |
escapeHTML
becomes true
by default.
allItems
template has been removed in favor of connectHits
CSS classes
Before |
After |
ais-hits |
ais-Hits |
ais-hits--empty |
ais-Hits--empty |
|
ais-Hits--list |
ais-hits--item |
ais-Hits--item |
Markup
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
| <div class="ais-Hits">
<ol class="ais-Hits-list">
<li class="ais-Hits-item">
Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
</li>
<li class="ais-Hits-item">
Hit 4397400: Google - Chromecast - Black
</li>
<li class="ais-Hits-item">
Hit 4397400: Google - Chromecast - Black
</li>
<li class="ais-Hits-item">
Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
</li>
<li class="ais-Hits-item">
Hit 4397400: Google - Chromecast - Black
</li>
<li class="ais-Hits-item">
Hit 4397400: Google - Chromecast - Black
</li>
<li class="ais-Hits-item">
Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
</li>
<li class="ais-Hits-item">
Hit 4397400: Google - Chromecast - Black
</li>
</ol>
</div>
|
HitPerPage (formerly HitsPerPageSelector)
Options
Before |
After |
attributeName |
attribute |
CSS classes
Before |
After |
|
ais-HitsPerPage |
ais-hits-per-page-selector |
ais-HitsPerPage-select |
ais-hits-per-page--item |
ais-HitsPerPage-option |
Markup
1
2
3
4
5
6
| <div class="ais-HitsPerPage">
<select class="ais-HitsPerPage-select">
<option class="ais-HitsPerPage-option" value="3">3 per page</option>
<option class="ais-HitsPerPage-option" value="6">6 per page</option>
</select>
</div>
|
Infinite hits
Options
Before |
After |
escapeHits |
escapeHTML |
loadMoreLabel |
templates.showMoreText |
escapeHTML
defaults to true
CSS classes
Before |
After |
ais-infinite-hits |
ais-InfiniteHits |
ais-infinite-hits--empty |
ais-InfiniteHits--empty |
|
ais-InfiniteHits--list |
ais-infinite-hits--item |
ais-InfiniteHits--item |
ais-infinite-hits--showmore |
|
ais-infinite-hits--showmoreButton |
ais-InfiniteHits-loadMore |
|
ais-InfiniteHits-loadMore--disabled |
Markup
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
| <div class="ais-InfiniteHits">
<ol class="ais-InfiniteHits-list">
<li class="ais-InfiniteHits-item">
Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
</li>
<li class="ais-InfiniteHits-item">
Hit 4397400: Google - Chromecast - Black
</li>
<li class="ais-InfiniteHits-item">
Hit 4397400: Google - Chromecast - Black
</li>
<li class="ais-InfiniteHits-item">
Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
</li>
<li class="ais-InfiniteHits-item">
Hit 4397400: Google - Chromecast - Black
</li>
<li class="ais-InfiniteHits-item">
Hit 4397400: Google - Chromecast - Black
</li>
<li class="ais-InfiniteHits-item">
Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
</li>
<li class="ais-InfiniteHits-item">
Hit 4397400: Google - Chromecast - Black
</li>
</ol>
<button class="ais-InfiniteHits-loadMore">Show more results</button>
|
Options
Before |
After |
|
showMore |
|
showMoreLimit |
CSS classes
Before |
After |
ais-hierarchical-menu |
ais-HierarchicalMenu |
|
ais-HierarchicalMenu--noRefinement |
|
ais-HierarchicalMenu-searchBox |
ais-hierarchical-menu--list |
ais-HierarchicalMenu-list |
|
ais-HierarchicalMenu-list--child |
|
ais-HierarchicalMenu-list--lvl0 |
|
ais-HierarchicalMenu-list--lvl1 |
ais-hierarchical-menu--item |
ais-HierarchicalMenu-item |
ais-hierarchical-menu--item__active |
ais-HierarchicalMenu-item--selected |
ais-hierarchical-menu--item__parent |
ais-HierarchicalMenu-item--parent |
ais-hierarchical-menu--link |
ais-HierarchicalMenu-link |
ais-hierarchical-menu--label |
ais-HierarchicalMenu-label |
ais-hierarchical-menu--count |
ais-HierarchicalMenu-count |
ais-hierarchical-menu--noResults |
ais-HierarchicalMenu-noResults |
|
ais-HierarchicalMenu-showMore |
|
ais-HierarchicalMenu-showMore--disabled |
Markup
Default
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
| <div class="ais-HierarchicalMenu">
<ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--lvl0">
<li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent ais-HierarchicalMenu-item--selected">
<a class="ais-HierarchicalMenu-link" href="#">
<span class="ais-HierarchicalMenu-label">Appliances</span>
<span class="ais-HierarchicalMenu-count">4,306</span>
</a>
<ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--child ais-HierarchicalMenu-list--lvl1">
<li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
<a class="ais-HierarchicalMenu-link" href="#">
<span class="ais-HierarchicalMenu-label">Dishwashers</span>
<span class="ais-HierarchicalMenu-count">181</span>
</a>
</li>
<li class="ais-HierarchicalMenu-item">
<a class="ais-HierarchicalMenu-link" href="#">
<span class="ais-HierarchicalMenu-label">Fans</span>
<span class="ais-HierarchicalMenu-count">91</span>
</a>
</li>
</ul>
</li>
<li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
<a class="ais-HierarchicalMenu-link" href="#">
<span class="ais-HierarchicalMenu-label">Audio</span>
<span class="ais-HierarchicalMenu-count">1,570</span>
</a>
</li>
</ul>
<button class="ais-HierarchicalMenu-showMore">Show more</button>
</div>
|
Show more disabled
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
| <div class="ais-HierarchicalMenu">
<ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--lvl0">
<li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent ais-HierarchicalMenu-item--selected">
<a class="ais-HierarchicalMenu-link" href="#">
<span class="ais-HierarchicalMenu-label">Appliances</span>
<span class="ais-HierarchicalMenu-count">4,306</span>
</a>
<ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--child ais-HierarchicalMenu-list--lvl1">
<li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
<a class="ais-HierarchicalMenu-link" href="#">
<span class="ais-HierarchicalMenu-label">Dishwashers</span>
<span class="ais-HierarchicalMenu-count">181</span>
</a>
</li>
<li class="ais-HierarchicalMenu-item">
<a class="ais-HierarchicalMenu-link" href="#">
<span class="ais-HierarchicalMenu-label">Fans</span>
<span class="ais-HierarchicalMenu-count">91</span>
</a>
</li>
</ul>
</li>
<li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
<a class="ais-HierarchicalMenu-link" href="#">
<span class="ais-HierarchicalMenu-label">Audio</span>
<span class="ais-HierarchicalMenu-count">1,570</span>
</a>
</li>
</ul>
<button class="ais-HierarchicalMenu-showMore ais-HierarchicalMenu-showMore--disabled" disabled>Show more</button>
</div>
|
Options
Before |
After |
attributeName |
attribute |
showMore.limit |
showMoreLimit |
showMore.templates.active |
templates.showMoreText |
showMore.templates.inactive |
templates.showMoreText |
showMore
is now a boolean option (showMore.templates
are now in templates
)
sortBy
defaults to ['isRefined', 'name:asc']
- An object containing
isShowingMore
is passed to showMoreText
template to toggle between the two states:
1
2
3
4
5
6
7
8
9
10
| {
showMoreText: `
{{#isShowingMore}}
Show less
{{/isShowingMore}}
{{^isShowingMore}}
Show more
{{/isShowingMore}}
`
}
|
CSS classes
Before |
After |
ais-menu |
ais-Menu |
ais-menu--list |
ais-Menu-list |
ais-menu--item |
ais-Menu-item |
ais-menu--item__active |
ais-Menu-item--selected |
ais-menu--link |
ais-Menu-link |
|
ais-Menu-label |
ais-menu--count |
ais-Menu-count |
|
ais-Menu-noResults |
|
ais-Menu-showMore |
|
ais-Menu-showMore--disabled |
Markup
Default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <div class="ais-Menu">
<ul class="ais-Menu-list">
<li class="ais-Menu-item ais-Menu-item--selected">
<a class="ais-Menu-link" href="#">
<span class="ais-Menu-label">Appliances</span>
<span class="ais-Menu-count">4,306</span>
</a>
</li>
<li class="ais-Menu-item">
<a class="ais-Menu-link" href="#">
<span class="ais-Menu-label">Audio</span>
<span class="ais-Menu-count">1,570</span>
</a>
</li>
</ul>
<button class="ais-Menu-showMore">Show more</button>
</div>
|
Show more disabled
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <div class="ais-Menu">
<ul class="ais-Menu-list">
<li class="ais-Menu-item ais-Menu-item--selected">
<a class="ais-Menu-link" href="#">
<span class="ais-Menu-label">Appliances</span>
<span class="ais-Menu-count">4,306</span>
</a>
</li>
<li class="ais-Menu-item">
<a class="ais-Menu-link" href="#">
<span class="ais-Menu-label">Audio</span>
<span class="ais-Menu-count">1,570</span>
</a>
</li>
</ul>
<button class="ais-Menu-showMore ais-Menu-showMore--disabled" disabled>Show more</button>
</div>
|
Options
Before |
After |
attributeName |
attribute |
CSS classes
Before |
After |
ais-menu-select |
ais-MenuSelect |
|
ais-MenuSelect--noRefinement |
ais-menu-select--select |
ais-MenuSelect-select |
ais-menu-select--option |
ais-MenuSelect-option |
ais-menu-select--header |
|
ais-menu-select--footer |
|
Markup
1
2
3
4
5
6
| <div class="ais-MenuSelect">
<select class="ais-MenuSelect-select">
<option class="ais-MenuSelect-option" value="Appliances">Appliances (4306)</option>
<option class="ais-MenuSelect-option" value="Audio">Audio (1570)</option>
</select>
</div>
|
Options
Before |
After |
attributeName |
attribute |
options |
items |
The item name
attribute is now named label
.
CSS classes
Before |
After |
ais-refinement-list |
ais-NumericMenu |
|
ais-NumericMenu--noRefinement |
ais-refinement-list--list |
ais-NumericMenu-list |
ais-refinement-list--item |
ais-NumericMenu-item |
ais-refinement-list--item__active |
ais-NumericMenu-item--selected |
ais-refinement-list--label |
ais-NumericMenu-label |
ais-refinement-list--radio |
ais-NumericMenu-radio |
|
ais-NumericMenu-labelText |
Markup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <div class="ais-NumericMenu">
<ul class="ais-NumericMenu-list">
<li class="ais-NumericMenu-item ais-NumericMenu-item--selected">
<label class="ais-NumericMenu-label">
<input class="ais-NumericMenu-radio" type="radio" name="NumericMenu" checked="" />
<span class="ais-NumericMenu-labelText">All</span>
</label>
</li>
<li class="ais-NumericMenu-item">
<label class="ais-NumericMenu-label">
<input class="ais-NumericMenu-radio" type="radio" name="NumericMenu" />
<span class="ais-NumericMenu-labelText">Less than 500</span>
</label>
</li>
</ul>
</div>
|
NumericSelector
Widget removed.
Options
Before |
After |
maxPages |
totalPages |
showFirstLast |
showFirst showLast |
|
showNext |
|
showPrevious |
labels |
templates |
labels.previous |
templates.previous |
labels.next |
templates.next |
labels.first |
templates.first |
labels.last |
templates.last |
CSS classes
Before |
After |
|
ais-Pagination |
|
ais-Pagination--noRefinement |
ais-pagination |
ais-Pagination-list |
ais-pagination--item |
ais-Pagination-item |
ais-pagination--item__first |
ais-Pagination-item--firstPage |
ais-pagination--item__last |
ais-Pagination-item--lastPage |
ais-pagination--item__previous |
ais-Pagination-item--previousPage |
ais-pagination--item__next |
ais-Pagination-item--nextPage |
|
ais-Pagination-item--page |
ais-pagination--item__active |
ais-Pagination-item--selected |
ais-pagination--item__disabled |
ais-Pagination-item--disabled |
ais-pagination--link |
ais-Pagination-link |
Markup
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
| <div class="ais-Pagination">
<ul class="ais-Pagination-list">
<li class="ais-Pagination-item ais-Pagination-item--firstPage ais-Pagination-item--disabled">
<span class="ais-Pagination-link" aria-label="Previous">‹‹</span>
</li>
<li class="ais-Pagination-item ais-Pagination-item--previousPage ais-Pagination-item--disabled">
<span class="ais-Pagination-link" aria-label="Previous">‹</span>
</li>
<li class="ais-Pagination-item ais-Pagination-item--selected">
<a class="ais-Pagination-link" href="#">1</a>
</li>
<li class="ais-Pagination-item ais-Pagination-item--page">
<a class="ais-Pagination-link" href="#">2</a>
</li>
<li class="ais-Pagination-item ais-Pagination-item--page">
<a class="ais-Pagination-link" href="#">3</a>
</li>
<li class="ais-Pagination-item">
<a class="ais-Pagination-link" href="#">4</a>
</li>
<li class="ais-Pagination-item ais-Pagination-item--nextPage">
<a class="ais-Pagination-link" aria-label="Next" href="#">›</a>
</li>
<li class="ais-Pagination-item ais-Pagination-item--lastPage">
<a class="ais-Pagination-link" aria-label="Next" href="#">››</a>
</li>
</ul>
</div>
|
PriceRanges
Widget removed.
Options
Before |
After |
attributeName |
attribute |
labels |
templates |
labels.separator |
templates.separatorText |
labels.submit |
templates.submitText |
CSS classes
Before |
After |
ais-range-input |
ais-RangeInput |
|
ais-RangeInput--noRefinement |
ais-range-input--body |
|
ais-range-input--form |
ais-RangeInput-form |
ais-range-input--fieldset |
|
|
ais-RangeInput-label |
ais-range-input--labelMin |
|
ais-range-input--labelMax |
|
|
ais-RangeInput-input |
ais-range-input--inputMin |
ais-RangeInput-input--min |
ais-range-input--inputMax |
ais-RangeInput-input--max |
ais-range-input--separator |
ais-RangeInput-separator |
ais-range-input--submit |
ais-RangeInput-submit |
Markup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <div class="ais-RangeInput">
<form class="ais-RangeInput-form">
<label class="ais-RangeInput-label">
<input class="ais-RangeInput-input ais-RangeInput-input--min" type="number" />
</label>
<span class="ais-RangeInput-separator">to</span>
<label class="ais-RangeInput-label">
<input class="ais-RangeInput-input ais-RangeInput-input--max" type="number" />
</label>
<button class="ais-RangeInput-submit" type="submit">Go</button>
</form>
</div>
|
RangeSlider
Options
Before |
After |
attributeName |
attribute |
CSS classes
Before |
After |
ais-range-slider |
ais-RangeSlider |
ais-range-slider--disabled |
ais-RangeSlider--disabled |
ais-range-slider--handle |
ais-RangeSlider-handle |
ais-range-slider--tooltip |
ais-RangeSlider-tooltip |
ais-range-slider--value |
ais-RangeSlider-value |
ais-range-slider--marker-horizontal |
ais-RangeSlider-marker--horizontal |
ais-range-slider--marker-large |
ais-RangeSlider-marker--large |
Markup
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
| <div class="ais-RangeSlider">
<div class="rheostat rheostat-horizontal" style="position: relative;">
<div class="rheostat-background"></div>
<div class="rheostat-handle rehostat-handle--lower" aria-valuemax="5000" aria-valuemin="1" aria-valuenow="750" aria-disabled="false" data-handle-key="0" role="slider" tabindex="0" style="left: 15%; position: absolute;">
<div class="rheostat-tooltip">$750</div>
</div>
<div class="rheostat-handle rheostat-handle--upper" aria-valuemax="5000" aria-valuemin="750" aria-valuenow="5000" aria-disabled="false" data-handle-key="1" role="slider" tabindex="0" style="left: 100%; position: absolute;">
<div class="rheostat-tooltip">$5,000</div>
</div>
<div class="rheostat-progress" style="left: 15%; width: 85%;"></div>
<div class="rheostat-marker rheostat-marker--large" style="left: 0%; position: absolute; margin-left: 0px;">
<div class="rheostat-value">1</div>
</div>
<div class="rheostat-marker" style="left: 2.94118%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 5.88235%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 8.82353%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 11.7647%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 14.7059%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 17.6471%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 20.5882%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 23.5294%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 26.4706%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 29.4118%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 32.3529%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 35.2941%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 38.2353%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 41.1765%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 44.1176%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 47.0588%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker rheostat-marker--large" style="left: 50%; position: absolute; margin-left: 0px;">
<div class="rheostat-value">2,500</div>
</div>
<div class="rheostat-marker" style="left: 52.9412%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 55.8824%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 58.8235%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 61.7647%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 64.7059%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 67.6471%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 70.5882%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 73.5294%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 76.4706%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 79.4118%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 82.3529%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 85.2941%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 88.2353%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 91.1765%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 94.1176%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker" style="left: 97.0588%; position: absolute; margin-left: 0px;"></div>
<div class="rheostat-marker rheostat-marker--large" style="left: 100%; position: absolute; margin-left: -1px;">
<div class="rheostat-value">5,000</div>
</div>
</div>
</div>
|
Options
Before |
After |
attributeName |
attribute |
labels.andUp |
Removed |
The value for the label andUp
is now inline inside templates.item
.
CSS classes
Before |
After |
ais-star-rating |
ais-RatingMenu |
ais-star-rating--list |
ais-RatingMenu-list |
ais-star-rating--item |
ais-RatingMenu-item |
ais-star-rating--item__active |
ais-RatingMenu-item--selected |
ais-star-rating--item__disabled |
ais-RatingMenu-item--disabled |
ais-star-rating--link |
ais-RatingMenu-link |
ais-star-rating--star |
ais-RatingMenu-starIcon |
|
ais-RatingMenu-starIcon--full |
ais-star-rating--star__empty |
ais-RatingMenu-starIcon--empty |
ais-star-rating--count |
ais-RatingMenu-count |
Markup
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
| <div class="ais-RatingMenu">
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="ais-RatingMenu-starSymbol" viewBox="0 0 24 24"><path d="M12 .288l2.833 8.718h9.167l-7.417 5.389 2.833 8.718-7.416-5.388-7.417 5.388 2.833-8.718-7.416-5.389h9.167z"/></symbol>
<symbol id="ais-RatingMenu-starEmptySymbol" viewBox="0 0 24 24"><path d="M12 6.76l1.379 4.246h4.465l-3.612 2.625 1.379 4.246-3.611-2.625-3.612 2.625 1.379-4.246-3.612-2.625h4.465l1.38-4.246zm0-6.472l-2.833 8.718h-9.167l7.416 5.389-2.833 8.718 7.417-5.388 7.416 5.388-2.833-8.718 7.417-5.389h-9.167l-2.833-8.718z"/></symbol>
</svg>
<ul class="ais-RatingMenu-list">
<li class="ais-RatingMenu-item ais-RatingMenu-item--disabled">
<div class="ais-RatingMenu-link" aria-label="5 & up" disabled>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
<span class="ais-RatingMenu-count">2,300</span>
</div>
</li>
<li class="ais-RatingMenu-item ais-RatingMenu-item--selected">
<a class="ais-RatingMenu-link" aria-label="4 & up" href="#">
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
<span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
<span class="ais-RatingMenu-count">2,300</span>
</a>
</li>
<li class="ais-RatingMenu-item">
<a class="ais-RatingMenu-link" aria-label="3 & up" href="#">
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
<svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
<span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
<span class="ais-RatingMenu-count">1,750</span>
</a>
</li>
</ul>
</div>
|
RefinementList
Options
Before |
After |
attributeName |
attribute |
searchForFacetValues |
searchable |
searchForFacetValues.placeholder |
searchablePlaceholder |
searchForFacetValues.isAlwaysActive |
searchableIsAlwaysActive |
searchForFacetValues.escapeFacetValues |
searchableEscapeFacetValues |
searchForFacetValues.templates.noResults |
templates.searchableNoResults |
showMore.templates.active |
templates.showMoreText |
showMore.templates.inactive |
templates.showMoreText |
searchablePlaceholder
defaults to "Search..."
searchableEscapeFacetValues
defaults to true
searchableIsAlwaysActive
defaults to true
showMore
is now a boolean option (searchForFacetValues.templates
and showMore.templates
are now in templates
)
- An object containing
isShowingMore
is passed to showMoreText
template to toggle between the two states:
1
2
3
4
5
6
7
8
9
10
| {
showMoreText: `
{{#isShowingMore}}
Show less
{{/isShowingMore}}
{{^isShowingMore}}
Show more
{{/isShowingMore}}
`
}
|
CSS classes
Before |
After |
ais-refinement-list |
ais-RefinementList |
|
ais-RefinementList--noRefinement |
|
ais-RefinementList-noResults |
ais-refinement-list--header |
|
ais-refinement-list--body |
|
ais-refinement-list--footer |
|
ais-refinement-list--list |
ais-RefinementList-list |
ais-refinement-list--item |
ais-RefinementList-item |
ais-refinement-list--item__active |
ais-RefinementList-item--selected |
ais-refinement-list--label |
ais-RefinementList-label |
ais-refinement-list--checkbox |
ais-RefinementList-checkbox |
|
ais-RefinementList-labelText |
ais-refinement-list--count |
ais-RefinementList-count |
|
ais-RefinementList-showMore |
|
ais-RefinementList-showMore--disabled |
Markup
Default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| <div class="ais-RefinementList">
<div class="ais-RefinementList-searchBox">
<!-- SearchBox widget here -->
</div>
<ul class="ais-RefinementList-list">
<li class="ais-RefinementList-item ais-RefinementList-item--selected">
<label class="ais-RefinementList-label">
<input class="ais-RefinementList-checkbox" type="checkbox" value="Insignia™" checked="" />
<span class="ais-RefinementList-labelText">Insignia™</span>
<span class="ais-RefinementList-count">746</span>
</label>
</li>
<li class="ais-RefinementList-item">
<label class="ais-RefinementList-label">
<input class="ais-RefinementList-checkbox" type="checkbox" value="Samsung">
<span class="ais-RefinementList-labelText">Samsung</span>
<span class="ais-RefinementList-count">633</span>
</label>
</li>
</ul>
<button class="ais-RefinementList-showMore">Show more</button>
</div>
|
Show more disabled
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| <div class="ais-RefinementList">
<div class="ais-RefinementList-searchBox">
<!-- SearchBox widget here -->
</div>
<ul class="ais-RefinementList-list">
<li class="ais-RefinementList-item ais-RefinementList-item--selected">
<label class="ais-RefinementList-label">
<input class="ais-RefinementList-checkbox" type="checkbox" value="Insignia™" checked="" />
<span class="ais-RefinementList-labelText">Insignia™</span>
<span class="ais-RefinementList-count">746</span>
</label>
</li>
<li class="ais-RefinementList-item">
<label class="ais-RefinementList-label">
<input class="ais-RefinementList-checkbox" type="checkbox" value="Samsung">
<span class="ais-RefinementList-labelText">Samsung</span>
<span class="ais-RefinementList-count">633</span>
</label>
</li>
</ul>
<button class="ais-RefinementList-showMore ais-RefinementList-showMore--disabled" disabled>Show more</button>
</div>
|
With search and no results
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
| <div class="ais-RefinementList">
<div class="ais-RefinementList-searchBox">
<div class="ais-SearchBox">
<form class="ais-SearchBox-form" novalidate>
<input class="ais-SearchBox-input" autocomplete="off" autocorrect="off" autocapitalize="off" placeholder="Search for products" spellcheck="false" maxlength="512" type="search" value="" />
<button class="ais-SearchBox-submit" type="submit" title="Submit the search query.">
<svg class="ais-SearchBox-submitIcon" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 40 40">
<path d="M26.804 29.01c-2.832 2.34-6.465 3.746-10.426 3.746C7.333 32.756 0 25.424 0 16.378 0 7.333 7.333 0 16.378 0c9.046 0 16.378 7.333 16.378 16.378 0 3.96-1.406 7.594-3.746 10.426l10.534 10.534c.607.607.61 1.59-.004 2.202-.61.61-1.597.61-2.202.004L26.804 29.01zm-10.426.627c7.323 0 13.26-5.936 13.26-13.26 0-7.32-5.937-13.257-13.26-13.257C9.056 3.12 3.12 9.056 3.12 16.378c0 7.323 5.936 13.26 13.258 13.26z"></path>
</svg>
</button>
<button class="ais-SearchBox-reset" type="reset" title="Clear the search query." hidden>
<svg class="ais-SearchBox-resetIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="10" height="10">
<path d="M8.114 10L.944 2.83 0 1.885 1.886 0l.943.943L10 8.113l7.17-7.17.944-.943L20 1.886l-.943.943-7.17 7.17 7.17 7.17.943.944L18.114 20l-.943-.943-7.17-7.17-7.17 7.17-.944.943L0 18.114l.943-.943L8.113 10z"></path>
</svg>
</button>
<span class="ais-SearchBox-loadingIndicator" hidden>
<svg width="16" height="16" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#444" class="ais-SearchBox-loadingIcon">
<g fill="none" fillRule="evenodd">
<g transform="translate(1 1)" strokeWidth="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18" />
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"
/>
</path>
</g>
</g>
</svg>
</span>
</form>
</div>
</div>
<div class="ais-RefinementList-noResults">No results.</div>
</div>
|
SearchBox
Options
Before |
After |
poweredBy |
use the poweredBy widget |
wrapInput |
use the connectSearchBox connector |
searchOnEnterKeyPressOnly |
searchAsYouType (default: true ) |
reset |
showReset |
magnifier |
showSubmit |
loadingIndicator |
showLoadingIndicator (default: true ) |
With the drop of wrapInput
, it was decided not to accept input
s as containers anymore. If you want complete control over the rendering, you should use the connectSearchBox
connector.
The search box doesn’t support poweredBy
.
Algolia requires that you use this widget if you’re on a community plan (open source, not-for-profit, or DocSearch).
Configuration options for reset
, submit
and loadingIndicator
have been moved to templates
and cssClasses
. For example, for reset
:
reset.template
=> templates.reset
reset.cssClasses.root
=> cssClasses.reset
autofocus
is now set to false
by default and doesn’t support the "auto"
value anymore.
CSS classes
Before |
After |
ais-search-box |
ais-SearchBox |
|
ais-SearchBox-form |
ais-search-box--input |
ais-SearchBox-input |
ais-search-box--magnifier-wrapper |
|
ais-search-box--magnifier |
ais-SearchBox-submit |
|
ais-SearchBox-submitIcon |
ais-search-box--reset-wrapper |
|
ais-search-box--reset |
ais-SearchBox-reset |
|
ais-SearchBox-resetIcon |
ais-search-box--loading-indicator-wrapper |
|
ais-search-box--loading-indicator |
ais-SearchBox-loadingIndicator |
|
ais-SearchBox-loadingIcon |
Markup
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
| <div class="ais-SearchBox">
<form class="ais-SearchBox-form" novalidate>
<input class="ais-SearchBox-input" autocomplete="off" autocorrect="off" autocapitalize="off" placeholder="Search for products" spellcheck="false" maxlength="512" type="search" value="" />
<button class="ais-SearchBox-submit" type="submit" title="Submit the search query.">
<svg class="ais-SearchBox-submitIcon" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 40 40">
<path d="M26.804 29.01c-2.832 2.34-6.465 3.746-10.426 3.746C7.333 32.756 0 25.424 0 16.378 0 7.333 7.333 0 16.378 0c9.046 0 16.378 7.333 16.378 16.378 0 3.96-1.406 7.594-3.746 10.426l10.534 10.534c.607.607.61 1.59-.004 2.202-.61.61-1.597.61-2.202.004L26.804 29.01zm-10.426.627c7.323 0 13.26-5.936 13.26-13.26 0-7.32-5.937-13.257-13.26-13.257C9.056 3.12 3.12 9.056 3.12 16.378c0 7.323 5.936 13.26 13.258 13.26z"></path>
</svg>
</button>
<button class="ais-SearchBox-reset" type="reset" title="Clear the search query." hidden>
<svg class="ais-SearchBox-resetIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="10" height="10">
<path d="M8.114 10L.944 2.83 0 1.885 1.886 0l.943.943L10 8.113l7.17-7.17.944-.943L20 1.886l-.943.943-7.17 7.17 7.17 7.17.943.944L18.114 20l-.943-.943-7.17-7.17-7.17 7.17-.944.943L0 18.114l.943-.943L8.113 10z"></path>
</svg>
</button>
<span class="ais-SearchBox-loadingIndicator" hidden>
<svg width="16" height="16" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#444" class="ais-SearchBox-loadingIcon">
<g fill="none" fillRule="evenodd">
<g transform="translate(1 1)" strokeWidth="2">
<circle strokeOpacity=".5" cx="18" cy="18" r="18" />
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"
/>
</path>
</g>
</g>
</svg>
</span>
</form>
<div>
|
SortBy
Options
Before |
After |
indices |
items |
- A
sortBy
item value is now value
instead of name
:
1
2
3
4
| const sortByItem = {
value: string,
label: string,
};
|
CSS classes
Before |
After |
|
ais-SortBy |
ais-sort-by-selector |
ais-SortBy-select |
ais-sort-by--item |
ais-SortBy-option |
Markup
1
2
3
4
5
6
| <div class="ais-SortBy">
<select class="ais-SortBy-select">
<option class="ais-SortBy-option" value="Most relevant">Most relevant</option>
<option class="ais-SortBy-option" value="Lowest price">Lowest price</option>
</select>
</div>
|
Stats
CSS classes
Before |
After |
ais-stats |
ais-Stats |
ais-stats--body |
|
ais-stats--time |
|
|
ais-Stats-text |
Markup
1
2
3
| <div class="ais-Stats">
<span class="ais-Stats-text">20,337 results found in 1ms.</span>
</div>
|
Options
Before |
After |
attributeName |
attribute |
collapsible |
|
autoHideContainer |
|
label |
templates.labelText |
templates.item |
|
values.on |
on |
values.off |
off |
collapsible
and autoHideContainer
options have been removed. These options are now implemented as part of the Panel widget wrapper.
The label
options has been moved into the templates.labelText
template to make it consistent with the templates parameters of other widgets and the item
template was removed. Data that was provided to templates.item
is now provided to templates.labelText
.
If your index attribute is called free_shipping
, the default template displays “free_shipping”. To rename it, change templates.labelText
to “Free shipping”.
CSS classes
Before |
After |
ais-toggle |
ais-ToggleRefinement |
ais-toggle--list |
|
ais-toggle--item |
|
|
ais-ToggleRefinement-label |
ais-toggle--checkbox |
ais-ToggleRefinement-checkbox |
ais-toggle--label |
ais-ToggleRefinement-labelText |
Markup
1
2
3
4
5
6
| <div class="ais-ToggleRefinement">
<label class="ais-ToggleRefinement-label">
<input class="ais-ToggleRefinement-checkbox" type="checkbox" value="Free Shipping" />
<span class="ais-ToggleRefinement-labelText">Free Shipping</span>
</label>
</div>
|
Connectors
connectAutocomplete
Options
Before |
After |
escapeHits |
escapeHTML |
escapeHTML
becomes true
by default.
connectBreadcrumb
- The BreadcrumbItem
name
property is renamed to label
.
connectGeoSearch
Options
Before |
After |
paddingBoundingBox |
Removed |
enableGeolocationWithIP |
Removed - use the Configure widget instead (see below) |
position |
Removed - use the Configure widget instead (see below) |
radius |
Removed - use the Configure widget instead (see below) |
precision |
Removed - use the Configure widget instead (see below) |
Since paddingBoundingBox
conflicted with the routing
option it was removed to support URLSync for the GeoSearch widget.
enableGeolocationWithIP
Before:
1
2
3
4
5
6
7
| const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
// Render implementation
});
customGeoSearch({
enableGeolocationWithIP: true,
});
|
After:
1
2
3
4
5
6
7
8
9
| const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
// Render implementation
});
instantsearch.widgets.configure({
aroundLatLngViaIP: true,
});
customGeoSearch();
|
position
Before:
1
2
3
4
5
6
7
| const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
// Render implementation
});
customGeoSearch({
position: { lat: 40.71, lng: -74.01 },
});
|
After:
1
2
3
4
5
6
7
8
9
| const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
// Render implementation
});
instantsearch.widgets.configure({
aroundLatLng: '40.71, -74.01',
});
customGeoSearch();
|
radius
Before:
1
2
3
4
5
6
7
| const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
// Render implementation
});
customGeoSearch({
radius: 1000,
});
|
After:
1
2
3
4
5
6
7
8
9
| const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
// Render implementation
});
instantsearch.widgets.configure({
aroundRadius: 1000,
});
customGeoSearch();
|
precision
Before:
1
2
3
4
5
6
7
| const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
// Render implementation
});
customGeoSearch({
precision: 1000,
});
|
After:
1
2
3
4
5
6
7
8
9
| const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
// Render implementation
});
instantsearch.widgets.configure({
aroundPrecision: 1000,
});
customGeoSearch();
|
connectRange
Options
Before |
After |
attributeName |
attribute |
connectRangeSlider
Connector removed (use connectRange
instead).
Options
Before |
After |
excludeAttributes |
excludedAttributes |
Options
Before |
After |
attributeName |
attribute |
options |
items |
connectRefinementList
Options
Before |
After |
attributeName |
attribute |
escapeFacetValues
defaults to true
connectCurrentRefinements (used to be connectCurrentRefinedValues)
Options
Before |
After |
clearsQuery |
excludedAttributes: [] |
items
(used to be refinements
)
refinements
used to be a flat list, now it is called items
(one per attribute), which each has its own list of refinements
Upgrade from v1 to v2
This guide has all the major changes that introduced in v2 with the description on how to
migrate from v1.
To identify the input as a search box, a magnifying glass icon was placed at the start, and a reset button displayed as a cross at the end.
You can customize the result with these two options:
- reset
boolan|{template?: string|Function, cssClasses?: {root: string}}
Display a reset button in the input when there is a query.
- magnifier
boolan|{template?: string|Function, cssClasses?: {root: string}}
Display a magnifier should at beginning of the input.
To make the search box like in v1, you can do the following:
1
2
3
4
5
6
7
8
| const search = instantsearch(/* Your parameters here */);
search.addWidgets([
instantsearch.widgets.searchbox({
container: '#your-search-input',
reset: false,
magnifier: false,
})
]);
|
You can read more about these options on the searchBox
API reference.
No more hitsPerPage
in hits
and infiniteHits
This option was removed from those two widgets. To configure
this option of the engine, there are still three ways:
- Use the dashboard or
the client,
to change the setting at the index level.
- Use the
hitsPerPage
widget.
- Use the configuration option of
instantsearch
:
1
2
3
4
5
6
| const search = instantsearch({
// ... do not forget the credentials
searchParameters: {
hitsPerPage: 42,
}
});
|
The default sort order of those widgets has changed. This might have affected your implementation
if you didn’t specify them originally. To change back the order use the sortBy
configuration
key.
Here are examples of usage of sortBy
using the previous sorting scheme:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| const yourSearch = instantsearch(/* parameters */);
yourSearch.addWidgets([
instantsearch.widgets.refinementList({
container: '#brands',
attributeName: 'brand',
// now the default is ['isRefined', 'count:desc', 'name:asc']
sortBy: ['count:desc', 'name:asc'],
}),
instantsearch.widgets.menu({
container: '#categories',
attributeName: 'categories',
// now the default is ['name:asc']
sortBy: ['count:desc', 'name:asc']
})
]);
|
If you want to learn more about sorting the values, check out the widget API to see what are
the valid values for the sortBy
option of menu
or
refinementList
Some variables have been changed
Internally all the widgets are now using the connectors. The aim was to ensure the API
was as close to the one offered by
react-instantsearch connectors.
This then affected the name of some variables in the templates and the API.
Changes in templates:
- In the item template of the hierarchicalMenu and menu widgets,
name
becomes label
- In the item template of the refinementList widget,
name
becomes value
.
Changes in the API:
- In hitsPerPageSelector, the
options
was renamed to items
.
React components can’t be used as templates
When InstantSearch.js was created it was built using React and it wasn’t known that react-instantsearch would be built. react-instantsearch is now the recommended solution if your app uses React. That’s why support for the React based templates was dropped in this release.
Algolia considers the engine used to build the widgets in InstantSearch.js to be an implementation detail. Since it isn’t exposed anymore, Algolia can change it and use the best solution for each release.
Slider components are hard to implement and that’s why Algolia relies on an external
component for that. Algolia is taking the opportunity of this new version
to switch to the current ‘state of the art’ of sliders: Rheostat.
If you want to customize the style, some CSS classes have changed. See some examples of style sheets.
The look is still similar as the one in the V1.
searchFunction can be used to change parameters
Introduced in 1.3.0, searchFunction
was originally meant as a way to change
the timing of the search. However it was realized that it was a good way to
alter the search state before making the actual state.
This is what’s required to force the query string:
1
2
3
4
5
6
7
| const search = instantsearch({
/* other parameters */
searchFunction(helper) {
search.helper.setQuery('fixed query');
helper.search();
}
});
|
And now, it’s more straightforward:
1
2
3
4
5
6
| const search = instantsearch({
/* other parameters */
searchFunction(helper) {
helper.setQuery('fixed query').search();
}
});
|
Bear in mind that the helper still resets the page to 0
when the parameters change. To keep
the previously set page you have to do the following:
1
2
3
4
5
6
7
8
9
| const search = instantsearch({
/* other parameters */
searchFunction(helper) {
const p = helper.getPage();
helper.setQuery('fixed query')
.setPage(p)
.search();
}
});
|