Skip to content

Commit cc97d66

Browse files
authoredDec 26, 2024··
refactor(algolia): simplify SearchBar component (#10801)
1 parent e8ad392 commit cc97d66

File tree

1 file changed

+99
-77
lines changed
  • packages/docusaurus-theme-search-algolia/src/theme/SearchBar

1 file changed

+99
-77
lines changed
 

‎packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx

+99-77
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import type {
3434
DocSearchModalProps,
3535
StoredDocSearchHit,
3636
DocSearchTransformClient,
37+
DocSearchHit,
3738
} from '@docsearch/react';
3839

3940
import type {AutocompleteState} from '@algolia/autocomplete-core';
@@ -50,6 +51,85 @@ type DocSearchProps = Omit<
5051

5152
let DocSearchModal: typeof DocSearchModalType | null = null;
5253

54+
function importDocSearchModalIfNeeded() {
55+
if (DocSearchModal) {
56+
return Promise.resolve();
57+
}
58+
return Promise.all([
59+
import('@docsearch/react/modal') as Promise<
60+
typeof import('@docsearch/react')
61+
>,
62+
import('@docsearch/react/style'),
63+
import('./styles.css'),
64+
]).then(([{DocSearchModal: Modal}]) => {
65+
DocSearchModal = Modal;
66+
});
67+
}
68+
69+
function useNavigator({
70+
externalUrlRegex,
71+
}: Pick<DocSearchProps, 'externalUrlRegex'>) {
72+
const history = useHistory();
73+
const [navigator] = useState<DocSearchModalProps['navigator']>(() => {
74+
return {
75+
navigate(params) {
76+
// Algolia results could contain URL's from other domains which cannot
77+
// be served through history and should navigate with window.location
78+
if (isRegexpStringMatch(externalUrlRegex, params.itemUrl)) {
79+
window.location.href = params.itemUrl;
80+
} else {
81+
history.push(params.itemUrl);
82+
}
83+
},
84+
};
85+
});
86+
return navigator;
87+
}
88+
89+
function useTransformSearchClient(): DocSearchModalProps['transformSearchClient'] {
90+
const {
91+
siteMetadata: {docusaurusVersion},
92+
} = useDocusaurusContext();
93+
return useCallback(
94+
(searchClient: DocSearchTransformClient) => {
95+
searchClient.addAlgoliaAgent('docusaurus', docusaurusVersion);
96+
return searchClient;
97+
},
98+
[docusaurusVersion],
99+
);
100+
}
101+
102+
function useTransformItems(props: Pick<DocSearchProps, 'transformItems'>) {
103+
const processSearchResultUrl = useSearchResultUrlProcessor();
104+
const [transformItems] = useState<DocSearchModalProps['transformItems']>(
105+
() => {
106+
return (items: DocSearchHit[]) =>
107+
props.transformItems
108+
? // Custom transformItems
109+
props.transformItems(items)
110+
: // Default transformItems
111+
items.map((item) => ({
112+
...item,
113+
url: processSearchResultUrl(item.url),
114+
}));
115+
},
116+
);
117+
return transformItems;
118+
}
119+
120+
function useResultsFooterComponent({
121+
closeModal,
122+
}: {
123+
closeModal: () => void;
124+
}): DocSearchProps['resultsFooterComponent'] {
125+
return useMemo(
126+
() =>
127+
({state}) =>
128+
<ResultsFooter state={state} onClose={closeModal} />,
129+
[closeModal],
130+
);
131+
}
132+
53133
function Hit({
54134
hit,
55135
children,
@@ -79,19 +159,15 @@ function ResultsFooter({state, onClose}: ResultsFooterProps) {
79159
);
80160
}
81161

82-
function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters {
83-
const normalize = (f: FacetFilters): FacetFilters =>
84-
typeof f === 'string' ? [f] : f;
85-
return [...normalize(f1), ...normalize(f2)];
86-
}
87-
88-
function DocSearch({
162+
function useSearchParameters({
89163
contextualSearch,
90-
externalUrlRegex,
91164
...props
92-
}: DocSearchProps) {
93-
const {siteMetadata} = useDocusaurusContext();
94-
const processSearchResultUrl = useSearchResultUrlProcessor();
165+
}: DocSearchProps): DocSearchProps['searchParameters'] {
166+
function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters {
167+
const normalize = (f: FacetFilters): FacetFilters =>
168+
typeof f === 'string' ? [f] : f;
169+
return [...normalize(f1), ...normalize(f2)];
170+
}
95171

96172
const contextualSearchFacetFilters =
97173
useAlgoliaContextualFacetFilters() as FacetFilters;
@@ -105,37 +181,27 @@ function DocSearch({
105181
: // ... or use config facetFilters
106182
configFacetFilters;
107183

108-
// We let user override default searchParameters if she wants to
109-
const searchParameters: DocSearchProps['searchParameters'] = {
184+
// We let users override default searchParameters if they want to
185+
return {
110186
...props.searchParameters,
111187
facetFilters,
112188
};
189+
}
190+
191+
function DocSearch({externalUrlRegex, ...props}: DocSearchProps) {
192+
const navigator = useNavigator({externalUrlRegex});
193+
const searchParameters = useSearchParameters({...props});
194+
const transformItems = useTransformItems(props);
195+
const transformSearchClient = useTransformSearchClient();
113196

114-
const history = useHistory();
115197
const searchContainer = useRef<HTMLDivElement | null>(null);
116-
// TODO remove after React 19 upgrade?
198+
// TODO remove "as any" after React 19 upgrade
117199
const searchButtonRef = useRef<HTMLButtonElement>(null as any);
118200
const [isOpen, setIsOpen] = useState(false);
119201
const [initialQuery, setInitialQuery] = useState<string | undefined>(
120202
undefined,
121203
);
122204

123-
const importDocSearchModalIfNeeded = useCallback(() => {
124-
if (DocSearchModal) {
125-
return Promise.resolve();
126-
}
127-
128-
return Promise.all([
129-
import('@docsearch/react/modal') as Promise<
130-
typeof import('@docsearch/react')
131-
>,
132-
import('@docsearch/react/style'),
133-
import('./styles.css'),
134-
]).then(([{DocSearchModal: Modal}]) => {
135-
DocSearchModal = Modal;
136-
});
137-
}, []);
138-
139205
const prepareSearchContainer = useCallback(() => {
140206
if (!searchContainer.current) {
141207
const divElement = document.createElement('div');
@@ -147,7 +213,7 @@ function DocSearch({
147213
const openModal = useCallback(() => {
148214
prepareSearchContainer();
149215
importDocSearchModalIfNeeded().then(() => setIsOpen(true));
150-
}, [importDocSearchModalIfNeeded, prepareSearchContainer]);
216+
}, [prepareSearchContainer]);
151217

152218
const closeModal = useCallback(() => {
153219
setIsOpen(false);
@@ -169,51 +235,7 @@ function DocSearch({
169235
[openModal],
170236
);
171237

172-
const navigator = useRef({
173-
navigate({itemUrl}: {itemUrl?: string}) {
174-
// Algolia results could contain URL's from other domains which cannot
175-
// be served through history and should navigate with window.location
176-
if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
177-
window.location.href = itemUrl!;
178-
} else {
179-
history.push(itemUrl!);
180-
}
181-
},
182-
}).current;
183-
184-
const transformItems = useRef<DocSearchModalProps['transformItems']>(
185-
(items) =>
186-
props.transformItems
187-
? // Custom transformItems
188-
props.transformItems(items)
189-
: // Default transformItems
190-
items.map((item) => ({
191-
...item,
192-
url: processSearchResultUrl(item.url),
193-
})),
194-
).current;
195-
196-
// @ts-expect-error: TODO fix lib issue after React 19, using JSX.Element
197-
const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
198-
useMemo(
199-
() =>
200-
// eslint-disable-next-line react/no-unstable-nested-components
201-
(footerProps: Omit<ResultsFooterProps, 'onClose'>): ReactNode =>
202-
<ResultsFooter {...footerProps} onClose={closeModal} />,
203-
[closeModal],
204-
);
205-
206-
const transformSearchClient = useCallback(
207-
(searchClient: DocSearchTransformClient) => {
208-
searchClient.addAlgoliaAgent(
209-
'docusaurus',
210-
siteMetadata.docusaurusVersion,
211-
);
212-
213-
return searchClient;
214-
},
215-
[siteMetadata.docusaurusVersion],
216-
);
238+
const resultsFooterComponent = useResultsFooterComponent({closeModal});
217239

218240
useDocSearchKeyboardEvents({
219241
isOpen,

0 commit comments

Comments
 (0)
Please sign in to comment.