// 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<ReturnType<AutoFilteringConnector>>["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 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,
};
},
};
};
};