@riadvargas/svelte-algolia-instantsearch
Version:
Svelte wrapper for Algolia InstantSearch
102 lines (101 loc) • 4.65 kB
JavaScript
import { readable, writable } from "svelte/store";
import algoliasearchHelper from "algoliasearch-helper";
import { getInstantSearchContext } from "./instantSearchContext";
import { getIndexContext } from "./indexContext";
import { onDestroyClientSide } from "./utils";
/*
createSearchResults and getIndexSearchResults were gotten from
https://github.com/algolia/react-instantsearch/blob/v6.31.1/packages/react-instantsearch-hooks/src/lib/getIndexSearchResults.ts and
https://github.com/algolia/react-instantsearch/blob/v6.31.1/packages/react-instantsearch-hooks/src/lib/createSearchResults.ts
*/
function createSearchResults(state) {
return new algoliasearchHelper.SearchResults(state, [
{
query: state.query ?? "",
page: state.page ?? 0,
hitsPerPage: state.hitsPerPage ?? 20,
hits: [],
nbHits: 0,
nbPages: 0,
params: "",
exhaustiveNbHits: true,
exhaustiveFacetsCount: true,
processingTimeMS: 0,
index: state.index,
},
], {
/** used by connectors to prevent persisting these results */
__isArtificial: true,
});
}
function getIndexSearchResults(indexWidget) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const helper = indexWidget.getHelper();
const results =
// On SSR, we get the results injected on the Index.
indexWidget.getResults() ||
// On the browser, we create fallback results based on the helper state.
createSearchResults(helper.state);
const scopedResults = indexWidget.getScopedResults().map((scopedResult) => {
const fallbackResults = scopedResult.indexId === indexWidget.getIndexId()
? results
: createSearchResults(scopedResult.helper.state);
return {
...scopedResult,
// We keep `results` from being `null`.
results: scopedResult.results || fallbackResults,
};
});
return {
results,
scopedResults,
};
}
export default function connect(connector, widgetParams = {}, additionalWidgetProperties = {}) {
const search = getInstantSearchContext();
const parentIndex = getIndexContext();
// Originally just used readable which means I had to initialize widget
// in the readable start function, however this keeps us from setting initial state.
// Could be nice to have something like widget.subscribe() :)
const writableState = writable();
const widget = { ...connector(writableState.set)(widgetParams), ...additionalWidgetProperties };
parentIndex.addWidgets([widget]);
onDestroyClientSide(() => {
parentIndex.removeWidgets([widget]);
});
// We want to get the initial state so that it will not be null at first render.
let initialState = {};
if (widget.getWidgetRenderState) {
// The helper exists because we've started InstantSearch.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const helper = parentIndex.getHelper();
const uiState = parentIndex.getWidgetUiState({})[parentIndex.getIndexId()];
helper.state = widget.getWidgetSearchParameters?.(helper.state, { uiState }) || helper.state;
const { results, scopedResults } = getIndexSearchResults(parentIndex);
// We get the widget render state by providing the same parameters as
// InstantSearch provides to the widget's `render` method.
// See https://github.com/algolia/instantsearch.js/blob/019cd18d0de6dd320284aa4890541b7fe2198c65/src/widgets/index/index.ts#L604-L617
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { widgetParams, ...renderState } = widget.getWidgetRenderState({
helper,
parent: parentIndex,
instantSearchInstance: search,
results,
scopedResults,
state: helper.state,
renderState: search.renderState,
templatesConfig: search.templatesConfig,
createURL: parentIndex.createURL,
searchMetadata: {
isSearchStalled: search.status === "stalled",
},
error: search.error,
status: search.status,
});
initialState = renderState;
}
// We set the writable state to the initial state so that it does not overwrite readable with null.
writableState.set(initialState);
// We return a readable because it makes more sense than a writable :)
return readable(initialState, (set) => writableState.subscribe(set));
}