Guides / Building Search UI

Upgrade Guides for InstantSearch

Upgrade event tracking

If you’ve been tracking events via the search-insights library, we’ve introduced a insights middleware to simplify the process further. We recommend you to upgrade and use the middleware instead of sending manual events.

Here are some benefits when using the insights middleware:

  • It better handles the userToken. Once you set it, all the search and event tracking calls properly include the token.
  • It automatically sends default events from built-in widgets such as refinementList, menu, etc. You can also modify the event payloads, or disable them altogether.
  • It lets you send custom events from your custom widgets.
  • It simplifies forwarding events to third-party trackers.

Update search-insights

The middleware is compatible with search-insights@1.6.3 or greater. If you’re using the UMD build, update the snippet like the following:

1
2
3
4
5
6
7
8
<script>
  var ALGOLIA_INSIGHTS_SRC = 'https://cdn.jsdelivr.net/npm/search-insights@1.6.3';

  !function(e,a,t,n,s,i,c){e.AlgoliaAnalyticsObject=s,e[s]=e[s]||function(){
  (e[s].queue=e[s].queue||[]).push(arguments)},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
2
3
npm install search-insights@1
# or
yarn add search-insights@1

The insights middleware

We’ve introduced the insights middleware since InstantSearch.js@4.8.0.

Check the insights middleware documentation to integrate it, then follow along for the rest of the migration.

No longer needed configuration

clickAnalytics: true

1
2
3
instantsearch.widgets.configure({
  clickAnalytics: true,
}),

This is not 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 bindEvent 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, bindEvent) {
      return `
          <article>
            <h3>${instantsearch.highlight({ attribute: 'name', hit })}</h3>

            <button
              ${bindEvent('conversion', hit, 'Product Added')}
            >
                Add to cart
                <!-- clicking this button sends a `conversion` event -->
            </button>
          </article>
        `;
    }
  }
})

analytics widget

We’ve deprecated the analytics widget 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 state 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
const indexName = '<your-index-name>';
const search = instantsearch({
  indexName,
  // ...
});

const analyticsMiddleware = () => {
  return {
    onStateChange({ uiState }) {
      // Google Analytics
      window.ga('set', 'page', window.location.pathname + window.location.search);
      window.ga('send', 'pageView');

      // 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
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 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 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. We provide a 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 are not using multi-index search, the way in which we store UI state 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,
        // ...
      },
    };
  },
};

Configure

The configure widget is now included in the UI state. If you want to exclude it from the URL you can use the default stateMappings 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 the URL does not apply to the state. Check the stateMapping source code for implementation details.

Helper

This release includes version 3 of the algoliasearch-helper package. If you are using the built-in widgets or connectors, nothing changes for you.

This version of algoliasearch-helper no longer includes Lodash, which significantly reduces its bundle size (from 27.5KB to 9.1KB Gzipped). If you’re using any methods from the helper, searchParameters or searchResults, please refer to the package’s detailed changelog.

searchParameters

We removed the option searchParameters from the instantsearch widget. You can easily 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 would otherwise be set during widget instantiation. Note that 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' }),
]);

addWidget and removeWidget

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.

Places.js widget

The widget which used to ship with Places.js is no longer compatible with InstantSearch. We have replaced it 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 params are:

  • placesReference: the places.js library.
  • defaultPosition: the coordinates to use if the user did not yet choose a location.
  • container: the DOM element to mount the input into.
  • ...other: places.js options

Custom widgets

Because we use version 3 of the algoliasearch-helper package, 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
}

pagination

The noRefinementRoot CSS class gets added once there are no more possible refinements. It no longer gets applied on the first page, which was incorrect behavior.

Upgrade from v2 to v3

InstantSearch v3 introduces some breaking changes in the widgets’ naming, options and markup.

The InstantSearch.js v2 documentation is available on the community website.

What’s new

Search from your own search client

Since InstantSearch.js 2.8.0, it is possible to search from your own back end using the searchClient option.

This option is now the way to plug InstantSearch to either our own official JavaScript client or to yours. This means that the official search client is not bundled in InstantSearch, allowing for more flexibility. This results in a smaller bundle size when you’re not searching directly from the front end, 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 our 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 application. It is more verbose and will give 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 application in a production environment.

Imports

UMD (Universal Module Definition)

We’re introducing a new naming 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

CJS (CommonJS)

Before
1
const instantsearch = require('instantsearch.js');
After
1
const instantsearch = require('instantsearch.js').default;

ES (ECMAScript)

No changes.

InstantSearch

appId and apiKey are replaced by searchClient

Previous usage
  1. Import InstantSearch.js
  2. Initialize InstantSearch
1
2
3
4
5
6
7
const search = instantsearch({
  indexName: 'indexName',
  appId: 'appId',
  apiKey: 'apiKey',
});

search.start();
New usage
  1. Import algoliasearch (prefer the lite version for search only)
  2. Import InstantSearch.js
  3. Initialize InstantSearch with the searchClient option
1
2
3
4
5
6
const search = instantsearch({
  indexName: 'indexName',
  searchClient: algoliasearch('appId', 'apiKey'),
});

search.start();

transformData is replaced by transformItems

Since InstantSearch.js first public release, we have provided an option to customize the values used in the widgets. This method was letting you map 1-1 the values with other values. With React Instantsearch, we implemented a slightly different API that allows to map over the list of values and to change their content.

Previous 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

One powerful feature to demonstrate to users why a result matched their query is highlighting. InstantSearch was relying on some internals to support this inside the template of the widgets (see below). We now have two dedicated helpers to support both highlighting and snippetting. You can find more information about that inside their documentation.

Previous usage
1
2
3
4
5
6
7
8
search.addWidgets([
  instantsearch.widget.hits({
    container: '#hits',
    templates: {
      item: '{{{ _highlightResult.name.value }}}',
    },
  })
]);
New usage
1
2
3
4
5
6
7
8
search.addWidgets([
  instantsearch.widget.hits({
    container: '#hits',
    templates: {
      item: '{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}',
    },
  })
]);

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.

Widgets

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>

ClearRefinements (formerly ClearAll)

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>

CurrentRefinements (formerly CurrentRefinedValues)

Options
Before After
attributes includedAttributes
  excludedAttributes
onlyListedAttributes use includedAttributes
clearsQuery use excludedAttributes
clearAll use the clearRefinements widget
  • clearsQuery can 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>

GeoSearch

Options
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)
  • paddingBoundingBox was in conflict with the routing option - so we removed it 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>

InfiniteHits

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>

Hierarchical Menu

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>

NumericMenu (formerly NumericRefinementList)

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.

Pagination

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.

RangeInput

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>

RatingMenu (formerly StarRating)

Options

Before After
attributeName attribute
labels.andUp Removed

The value for the label andUp is now inlined 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>
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, we decided not to accept inputs as containers anymore. If you want complete control over the rendering, you should use the searchBox connector.

The search box does not support poweredBy. If you’re using our free plan, you need to use the poweredBy widget to display the Algolia logo.

Configuration options for reset, submit and loadingIndicator have been moved to templates and cssClasses. For example, in the case of reset:

  • reset.template => templates.reset
  • reset.cssClasses.root => cssClasses.reset

Finally, autofocus is now set to false by default and does not 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>

ToggleRefinement (formerly Toggle)

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 we removed the item template. We are now providing the data that were provided to templates.item to templates.labelText. If your index attribute is called free_shipping, the default template will display “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)
  • paddingBoundingBox was in conflict with the routing option - so we removed it 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).

connectClearRefinements (formerly connectClearAll)

Options
Before After
excludeAttributes excludedAttributes

connectNumericMenu (formerly connectNumericRefinementList)

Options
Before After
attributeName attribute
options items
  • name becomes label

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 contains all the major changes that were introduced in v2 with the description on how to migrate from v1.

The InstantSearch.js v1 documentation is available on the community website.

The searchBox widget has new default options

To identify correctly the input as a search box we added a magnifier glass at the start of the input and a reset button displayed as a cross on 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 has been 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 items are not sorted like before in the refinementList / menu

We changed the default sort order of those widgets. This might have impacted 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. And we wanted their API to be close to the one offered by react-instantsearch connectors. This then impacted 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 has been renamed to items.

React components can’t be used as templates

When we created InstantSearch.js, it was built using React and we didn’t know that we would build react-instantsearch. However, we have now react-instantsearch and it is the recommended solution if your application uses React. That’s why we’re dropping in this version the support for the react based templates.

As of now, we consider the engine used to build the widgets in InstantSearch.js as an implementation detail. Since we do not expose it anymore, we’ll be able to change it and use the best solution for each release.

RangeSlider widget is using Rheostat as Slider Component

Slider compoments are hard to implement and that’s why we rely on an external component for that. We are 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. You can find examples of stylesheets by clicking here.

We are still providing a look which is similar as the one in the V1.

searchFunction can be used to modify parameters

Introduced in 1.3.0, searchFunction was originally meant as a way to modify the timing of the search. However we realized that it was a good way to alter the search state before making the actual state.

This is what was 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 is 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. So in order 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();
  }
});

Did you find this page helpful?