import { useCallback, useEffect, useMemo, useState } from 'react'; import { MatchHandler, AsyncSearch, AsyncSearchHandler, AsyncSearchOption, MatchQueryOption, NormalizeOption, normalize, matchQuery, ResultHandler, } from '../utils/AsyncSearch'; export type UseAsyncSearchOptions = AsyncSearchOption & { matchOptions?: MatchQueryOption; normalizeOptions?: NormalizeOption; }; export type SearchItemStrGetter = ( searchItem: TSearchItem ) => string | string[]; export type UseAsyncSearchResult = { query: string; items: TSearchItem[]; }; export const useAsyncSearch = ( list: TSearchItem[], getItemStr: SearchItemStrGetter, options?: UseAsyncSearchOptions ): [UseAsyncSearchResult | undefined, AsyncSearchHandler] => { const [result, setResult] = useState>(); const [searchCallback, terminateSearch] = useMemo(() => { setResult(undefined); const handleMatch: MatchHandler = (item, query) => { const itemStr = getItemStr(item); if (Array.isArray(itemStr)) return !!itemStr.find((i) => matchQuery(normalize(i, options?.normalizeOptions), query, options?.matchOptions) ); return matchQuery( normalize(itemStr, options?.normalizeOptions), query, options?.matchOptions ); }; const handleResult: ResultHandler = (results, query) => setResult({ query, items: results, }); return AsyncSearch(list, handleMatch, handleResult, options); }, [list, options, getItemStr]); const searchHandler: AsyncSearchHandler = useCallback( (query) => { const normalizedQuery = normalize(query, options?.normalizeOptions); if (!normalizedQuery) { setResult(undefined); return; } searchCallback(normalizedQuery); }, [searchCallback, options?.normalizeOptions] ); useEffect( () => () => { // terminate any ongoing search request on unmount. terminateSearch(); }, [terminateSearch] ); return [result, searchHandler]; };