Skip to content

Commit

Permalink
fix(rendering): ensure resilience against "null" results (#6442)
Browse files Browse the repository at this point in the history
* fix(rendering): ensure resilience against "null" results

In some cases `results` in `render` can be null:
- no index name passed
- stalled render

In the vast majority of cases we already handled `results` being `null` or `undefined` just in case, but now i've changed the type to ensure we always catch this problem

This issue doesn't have a reproduction, but i believe it now guards for all possible cases of "no results", so:
fixes #6441

[CR-7353]

* type

* types

* margin
  • Loading branch information
Haroenv authored Dec 9, 2024
1 parent 00aff64 commit a3f0e18
Show file tree
Hide file tree
Showing 17 changed files with 38 additions and 29 deletions.
4 changes: 2 additions & 2 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
"maxSize": "83.50 kB"
"maxSize": "84 kB"
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "181.50 kB"
"maxSize": "182 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const { panel, clearRefinements } = window.instantsearch.widgets;

const clearFilters = panel<typeof clearRefinements>({
hidden(options) {
return options.results.nbHits > 0;
hidden({ results }) {
return Boolean(results && results.nbHits > 0);
},
})(clearRefinements);

Expand Down
2 changes: 1 addition & 1 deletion examples/js/e-commerce-umd/src/widgets/Pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { pagination: paginationWidget, panel } = window.instantsearch.widgets;

const paginationWithMultiplePages = panel<typeof paginationWidget>({
hidden({ results }) {
return results.nbPages <= 1;
return Boolean(results && results.nbPages <= 1);
},
})(paginationWidget);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { panel, clearRefinements } from 'instantsearch.js/es/widgets';

const clearFilters = panel<typeof clearRefinements>({
hidden(options) {
return options.results.nbHits > 0;
hidden({ results }) {
return Boolean(results && results.nbHits > 0);
},
})(clearRefinements);

Expand Down
2 changes: 1 addition & 1 deletion examples/js/e-commerce/src/widgets/Pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {

const paginationWithMultiplePages = panel<typeof paginationWidget>({
hidden({ results }) {
return results.nbPages <= 1;
return Boolean(results && results.nbPages <= 1);
},
})(paginationWidget);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,21 +183,23 @@ search.addWidgets([
const indices = scopedResults.map((scopedResult) => {
// We need to escape the hits because highlighting
// exposes HTML tags to the end-user.
scopedResult.results.hits = escapeHTML
? escapeHits(scopedResult.results.hits)
: scopedResult.results.hits;
if (scopedResult.results) {
scopedResult.results.hits = escapeHTML
? escapeHits(scopedResult.results.hits)
: scopedResult.results.hits;
}

const sendEvent = createSendEventForHits({
instantSearchInstance,
getIndex: () => scopedResult.results.index,
getIndex: () => scopedResult.results?.index || '',
widgetType: this.$$type,
});

return {
indexId: scopedResult.indexId,
indexName: scopedResult.results.index,
hits: scopedResult.results.hits,
results: scopedResult.results,
indexName: scopedResult.results?.index || '',
hits: scopedResult.results?.hits || [],
results: scopedResult.results || ({} as unknown as SearchResults),
sendEvent,
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ function getAttributesToClear({
includedAttributes: string[];
excludedAttributes: string[];
transformItems: TransformItems<string>;
results: SearchResults | undefined;
results: SearchResults | undefined | null;
}): AttributesToClear {
const includesQuery =
includedAttributes.indexOf('query') !== -1 ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ const connectCurrentRefinements: CurrentRefinementsConnector =
if (!results) {
return transformItems(
getRefinementsItems({
results: {},
results: null,
helper,
indexId: helper.state.index,
includedAttributes,
Expand Down Expand Up @@ -282,7 +282,7 @@ function getRefinementsItems({
includedAttributes,
excludedAttributes,
}: {
results: SearchResults | Record<string, never>;
results: SearchResults | null;
helper: AlgoliaSearchHelper;
indexId: string;
includedAttributes: CurrentRefinementsConnectorParams['includedAttributes'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export type InfiniteHitsRenderState<
/**
* The response from the Algolia API.
*/
results?: SearchResults<Hit<THit>>;
results?: SearchResults<Hit<THit>> | null;

/**
* The banner to display above the hits.
Expand Down Expand Up @@ -435,7 +435,7 @@ export default (function connectInfiniteHits<
sendEvent,
bindEvent,
banner,
results,
results: results || undefined,
showPrevious,
showMore,
isFirstPage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const connectHits =
$$type: 'ais.hits',
init() {},
render({ results, instantSearchInstance }) {
const hits = results.hits;
const hits = results?.hits;
renderFn({ hits, results, instantSearchInstance, widgetParams }, false);
},
dispose() {
Expand Down
3 changes: 2 additions & 1 deletion packages/instantsearch.js/src/lib/utils/getRefinements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,11 @@ function getRefinement(
}

export function getRefinements(
results: SearchResults | Record<string, never>,
_results: SearchResults | Record<string, never> | null,
state: SearchParameters,
includesQuery: boolean = false
): Refinement[] {
const results = _results || {};
const refinements: Refinement[] = [];
const {
facetsRefinements = {},
Expand Down
2 changes: 1 addition & 1 deletion packages/instantsearch.js/src/lib/utils/render-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function createRenderArgs(
parent: IndexWidget,
widget: IndexWidget | Widget
) {
const results = parent.getResultsForWidget(widget)!;
const results = parent.getResultsForWidget(widget);
const helper = parent.getHelper()!;

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,10 @@ export function createInsightsMiddleware<
instantSearchInstance.mainHelper!.derivedHelpers[0].on(
'result',
({ results }) => {
if (!results.queryID || results.queryID !== lastQueryId) {
if (
results &&
(!results.queryID || results.queryID !== lastQueryId)
) {
lastQueryId = results.queryID;
viewedObjectIDs.clear();
}
Expand Down
6 changes: 3 additions & 3 deletions packages/instantsearch.js/src/types/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {

export type ScopedResult = {
indexId: string;
results: SearchResults;
results: SearchResults | null;
helper: Helper;
};

Expand Down Expand Up @@ -45,7 +45,7 @@ export type InitOptions = SharedRenderOptions & {
export type ShouldRenderOptions = { instantSearchInstance: InstantSearch };

export type RenderOptions = SharedRenderOptions & {
results: SearchResults;
results: SearchResults | null;
};

export type DisposeOptions = {
Expand Down Expand Up @@ -339,7 +339,7 @@ export type Widget<
export type { IndexWidget } from '../widgets';

export type TransformItemsMetadata = {
results?: SearchResults;
results: SearchResults | undefined | null;
};

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/instantsearch.js/src/widgets/analytics/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ For the migration, visit https://www.algolia.com/doc/guides/building-search-ui/u
},

render({ results, state }) {
if (!results) {
return;
}
if (isInitialSearch === true) {
isInitialSearch = false;

Expand Down
2 changes: 1 addition & 1 deletion packages/instantsearch.js/stories/panel.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ storiesOf('Basics/Panel', module)
header: () => 'Price',
footer: () => 'The panel is hidden when there are no results.',
},
hidden: ({ results }) => results.nbHits === 0,
hidden: ({ results }) => results?.nbHits === 0,
})(instantsearch.widgets.rangeInput);

search.addWidgets([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type SearchResultsApi = {
export function useSearchResults(): SearchResultsApi {
const search = useInstantSearchContext();
const searchIndex = useIndexContext();
const [searchResults, setSearchResults] = useState(() => {
const [searchResults, setSearchResults] = useState<SearchResultsApi>(() => {
const indexSearchResults = getIndexSearchResults(searchIndex);
// We do this not to leak `recommendResults` in the API.
return {
Expand Down

0 comments on commit a3f0e18

Please sign in to comment.