Control Automatic Filtering and Boosting in your UI
If you want to have Automatic Filtering and Boosting on or off for all searches, you only need to enable the feature to filter the results automatically. If you want to let users turn Automatic Filtering and Boosting on or off, you can build an InstantSearch widget. This widget should inform users that the results are filtered. The widget should also let users remove any applied filters.Build a widget for Automatic Filtering and Boosting
1
Check the search response
The search response includes the following properties if Automatic Filtering and Boosting is enabled:If filters are applied to the query, they’re listed in the
JSON
{
"extensions": {
"queryCategorization": {
"autofiltering": {
"facetFilters": [],
"optionalFilters": []
}
}
}
}
facetFilters property.JSON
{
"facetFilters": [
"category.level0:Fresh products",
"category.level1:Fresh vegetables",
"category.level2:Tomatoes"
]
}
2
Let users remove applied filters
To remove filters applied with Automatic Filtering and Boosting, you need to turn the feature off for the current query using the
enableAutoFiltering API parameter.JSON
{
"extensions": {
"queryCategorization": {
"enableAutoFiltering": false
}
}
}
3
Turn on automatic filtering and boosting for new queries
To keep Automatic Filtering and Boosting for other queries,
check for a query change and then set
enableAutoFiltering to true.Implementing the widget
The following custom connector implements all steps to let users turn off Automatic Filtering and Boosting for a search query.The connector requires the Algolia search helper.
//connectAutoFiltering.js
// util function to set the query parameter (step 2)
function setAutoFiltering(value, helper) {
return helper.setQueryParameter('extensions', {
queryCategorization: {
enableAutoFiltering: value,
},
});
}
export function connectAutoFiltering(renderFn, unmountFn = () => {}) {
return function autoFiltering(widgetParams) {
const connectorState = {};
return {
$$type: 'algolia.beta.autoFiltering',
init(initOptions) {
const { instantSearchInstance } = initOptions;
renderFn(
{
...this.getWidgetRenderState(initOptions),
instantSearchInstance,
},
true
);
},
render(renderOptions) {
const { instantSearchInstance } = renderOptions;
renderFn(
{
...this.getWidgetRenderState(renderOptions),
instantSearchInstance,
},
false
);
},
dispose() {
unmountFn();
},
getWidgetSearchParameters(searchParameters) {
return searchParameters;
},
getRenderState(renderState, renderOptions) {
return {
...renderState,
autoFiltering: this.getWidgetRenderState(renderOptions),
};
},
// this is where the logic happens
getWidgetRenderState({ results, helper, state }) {
if (!connectorState.cancelAutoFiltering) {
// exposing a function to disable autofiltering
connectorState.cancelAutoFiltering = () => {
// Disable auto filtering for next search
setAutoFiltering(false, helper);
helper.search();
// storing in the state the disabled query
connectorState.disabledQuery = helper.getQuery().query;
};
}
// empty results case
if (!results) {
return {
appliedFilters: [],
cancelAutoFiltering: () => {},
widgetParams,
};
}
// enabling back auto filtering if the query has changed (step 3)
if (
// "state" stores the current query parameters
state.extensions &&
state.extensions.queryCategorization.enableAutoFiltering === false &&
connectorState.disabledQuery &&
results.query !== connectorState.disabledQuery
) {
setAutoFiltering(true, helper);
if (
results.extensions &&
results.extensions.queryCategorization.normalizedQuery
) {
// if the current query has predicted categories, we refine the search with autofiltering enabled
helper.search();
}
}
// Retrieving the applied filters (step 1)
const facetFilters =
(results.extensions &&
results.extensions.queryCategorization.autofiltering &&
results.extensions.queryCategorization.autofiltering
.facetFilters) ||
[];
return {
appliedFilters:
facetFilters.map((facetFilter) => ({
name: facetFilter.split(':')[0],
value: facetFilter.split(':')[1],
})),
cancelAutoFiltering: connectorState.cancelAutoFiltering,
widgetParams,
};
},
};
};
}
// connectAutoFiltering.ts
import type { Connector } from 'instantsearch.js';
export type AutoFilteringConnectorParams = {};
export type AutoFilteringRenderState = {
appliedFilters: Array<{
name: string;
value: string;
}>;
cancelAutoFiltering(): void;
};
export type AutoFilteringWidgetDescription = {
$$type: 'algolia.beta.autoFiltering';
renderState: AutoFilteringRenderState;
indexRenderState: {};
indexUiState: {};
};
type AutoFilteringConnector = Connector<
AutoFilteringWidgetDescription,
AutoFilteringConnectorParams
>;
type ConnectorState = {
disabledQuery?: string;
cancelAutoFiltering?: AutoFilteringRenderState['cancelAutoFiltering'];
};
// util function to set the query parameter (step 2)
function setAutoFiltering(value, helper) {
return helper.setQueryParameter('extensions', {
queryCategorization: {
enableAutoFiltering: value,
},
});
}
type WidgetRenderStateWithResultsExtensions = Parameters<
ReturnType>['getWidgetRenderState']
>[0] & {
state: {
extensions?: {
queryCategorization?: {
enableAutoFiltering?: boolean;
};
};
};
results: {
extensions?: {
queryCategorization?: {
normalizedQuery?: string;
autofiltering?: {
facetFilters?: string[];
};
};
};
};
};
export const connectAutoFiltering: AutoFilteringConnector = (
renderFn,
unmountFn = () => {}
) => {
return function autoFiltering(widgetParams) {
const connectorState: ConnectorState = {};
return {
$$type: 'algolia.beta.autoFiltering',
init(initOptions) {
const { instantSearchInstance } = initOptions;
renderFn(
{
...this.getWidgetRenderState(initOptions),
instantSearchInstance,
},
true
);
},
render(renderOptions) {
const { instantSearchInstance } = renderOptions;
renderFn(
{
...this.getWidgetRenderState(renderOptions),
instantSearchInstance,
},
false
);
},
dispose() {
unmountFn();
},
getWidgetUiState(uiState) {
return uiState;
},
getWidgetSearchParameters(searchParameters) {
return setAutoFiltering(true, searchParameters);
},
getRenderState(renderState, renderOptions) {
return {
...renderState,
autoFiltering: this.getWidgetRenderState(renderOptions),
};
},
// this is where the logic happens
getWidgetRenderState({
results,
helper,
state,
}: WidgetRenderStateWithResultsExtensions) {
if (!connectorState.cancelAutoFiltering) {
connectorState.cancelAutoFiltering = () => {
// Disable auto filtering for next search
setAutoFiltering(false, helper);
helper.search();
// storing in the state the disabled query
connectorState.disabledQuery = helper.getQuery().query;
};
}
// empty results case
if (!results) {
return {
appliedFilters: [],
cancelAutoFiltering: () => {},
widgetParams,
};
}
// enabling back auto filtering if the query has changed (step 3)
if (
// "state" stores the current query parameters
state.extensions?.queryCategorization?.enableAutoFiltering ===
false &&
connectorState.disabledQuery &&
results.query !== connectorState.disabledQuery
) {
setAutoFiltering(true, helper);
if (results.extensions.queryCategorization.normalizedQuery) {
// if the current query has predicted categories, we refine the search with autofiltering enabled
helper.search();
}
}
// Retrieving the applied filters (step 1)
const facetFilters = results.extensions?.queryCategorization?.autofiltering
?.facetFilters || [];
return {
appliedFilters:
facetFilters.map((facetFilter) => ({
name: facetFilter.split(':')[0],
value: facetFilter.split(':')[1],
})),
cancelAutoFiltering: connectorState.cancelAutoFiltering,
widgetParams,
};
},
};
};
};
// autofiltering.js
import { connectAutoFiltering } from './connectAutoFiltering';
export function autoFiltering(widgetParams) {
const { container: containerSelector } = widgetParams;
if (!containerSelector) {
console.error('The `container` option is required.');
}
const container = document.querySelector(containerSelector);
// the widget render function
const renderer = (renderOptions, isFirstRender) => {
const { appliedFilters, cancelAutoFiltering } = renderOptions;
/**
* Here we choose to display the applied filters in a banner with a button to disable autofiltering
* We are displaying only the last filter of the hierarchy, but you can chose to display all of them if you want to
* It is entirely up to you how you choose to render the filters
*/
container.innerHTML = appliedFilters.length
? `
Applied filter:
${appliedFilters.pop().value}
×
`
: '';
const button = container.querySelector('button');
if (button) {
// attaching our cancel function to our button
button.addEventListener('click', () => {
cancelAutoFiltering();
});
}
};
const makeWidget = connectAutoFiltering(renderer, () => {
container.innerHTML = '';
});
return {
...makeWidget({}),
$$widgetType: 'algolia.beta.autoFiltering',
};
}
// autofiltering.ts
import { connectAutoFiltering } from './connectAutoFiltering';
import type { AutoFilteringRenderState } from './connectAutoFiltering';
type WidgetParams = {
container: string;
};
export function autoFiltering(widgetParams: WidgetParams) {
const { container: containerSelector } = widgetParams;
if (!containerSelector) {
console.error('The `container` option is required.');
}
const container = document.querySelector(containerSelector);
// the widget render function
const renderer = (
renderOptions: AutoFilteringRenderState,
isFirstRender: boolean
) => {
const { appliedFilters, cancelAutoFiltering } = renderOptions;
/**
* Here we choose to display the applied filters in a banner with a button to disable autofiltering
* We are displaying only the last filter of the hierarchy, but you can chose to display all of them if you want to
* It is entirely up to you how you choose to render the filters
*/
container.innerHTML =
appliedFilters && appliedFilters.length
? `
Applied filter:
${appliedFilters.pop().value}
×
`
: '';
const button = container.querySelector('button');
if (button) {
// attaching our cancel function to our button
button.addEventListener('click', () => {
cancelAutoFiltering();
});
}
};
const makeWidget = connectAutoFiltering(renderer, () => {
container.innerHTML = '';
});
return {
...makeWidget({}),
$$widgetType: 'algolia.beta.autoFiltering',
};
}
JavaScript
import { autoFiltering } from './autoFiltering';
// ...
const search = instantsearch({
indexName,
searchClient,
});
search.addWidgets([
// ...
autoFiltering({
container: '#autofiltering-container',
}),
]);