1
0
Fork 0
docsearch/packages/docsearch-react/src/hooks/useDocSearchFacets.ts
Paul Jankowski 4f6b5b1b88
feat(v5): UI and DX improvements (#2949)
* feat: Rename assistantId to agentId

* feat: Allow reading default facet values from index searchParameters

* feat: Remove indexName prop from Sidepanel, cleanup documentation pages

* feat: Move appId and apiKey up into @docsearch/core

* feat: Add back nested grouping of search results

* add changeset

* revert changes to example demo

* fix: e2e tests
2026-08-04 09:43:08 -04:00

111 lines
3.2 KiB
TypeScript

import React from 'react';
import type { FacetBarFacet } from '../components/FacetBar';
import type { DocSearchFacet, DocSearchIndex } from '../DocSearch';
import { useFacetValues } from '../useFacetValues';
import type { useSearchClient } from '../useSearchClient';
import type { FacetSelections } from '../utils/createDocSearchSources';
import {
deriveDefaultSelectedFacetsFromIndex,
normalizeFacets,
} from '../utils/facets';
export interface UseDocSearchFacetsProps {
facets?: DocSearchFacet[];
indexes: DocSearchIndex[];
searchClient: ReturnType<typeof useSearchClient>;
/**
* Called after any facet selection change. Modals use this to refresh the
* autocomplete results.
*/
onSelectionsChange?: () => void;
}
export interface UseDocSearchFacetsResult {
/** Facets that have at least one value, ready to be rendered by `FacetBar`. */
visibleFacets: FacetBarFacet[];
facetSelections: FacetSelections;
/** Always-current selections, for consumption inside `getSources` closures. */
facetSelectionsRef: React.MutableRefObject<FacetSelections>;
handleFacetSelectionChange: (facet: string, value: string) => void;
clearFacetSelections: () => void;
}
export function useDocSearchFacets({
facets,
indexes,
searchClient,
onSelectionsChange,
}: UseDocSearchFacetsProps): UseDocSearchFacetsResult {
const normalizedFacets = React.useMemo(
() => normalizeFacets(facets),
[facets]
);
const normalizedFacetsRef = React.useRef(normalizedFacets);
const facetValues = useFacetValues({
facets: normalizedFacets,
indexes,
searchClient,
});
const [facetSelections, setFacetSelections] = React.useState<FacetSelections>(
() => deriveDefaultSelectedFacetsFromIndex(indexes)
);
const facetSelectionsRef = React.useRef(facetSelections);
const onSelectionsChangeRef = React.useRef(onSelectionsChange);
const visibleFacets = React.useMemo(
() =>
normalizedFacets
.map((facet) => ({ ...facet, values: facetValues[facet.key] ?? [] }))
.filter((facet) => facet.values.length > 0),
[facetValues, normalizedFacets]
);
React.useLayoutEffect(() => {
normalizedFacetsRef.current = normalizedFacets;
onSelectionsChangeRef.current = onSelectionsChange;
});
const applySelections = React.useCallback((next: FacetSelections): void => {
facetSelectionsRef.current = next;
setFacetSelections(next);
onSelectionsChangeRef.current?.();
}, []);
const handleFacetSelectionChange = React.useCallback(
(facet: string, value: string): void => {
const existing = facetSelectionsRef.current[facet];
if (existing === value) return;
const next = { ...facetSelectionsRef.current };
next[facet] = value;
applySelections(next);
},
[applySelections]
);
const clearFacetSelections = React.useCallback(() => {
if (Object.keys(facetSelectionsRef.current).length === 0) return;
const next = {};
for (const facet of normalizedFacetsRef.current) {
if (facetSelectionsRef.current[facet.key]) {
next[facet.key] = '';
}
}
applySelections(next);
}, [applySelections]);
return {
visibleFacets,
facetSelections,
facetSelectionsRef,
handleFacetSelectionChange,
clearFacetSelections,
};
}