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, query: string ) => string | string[]; export type UseAsyncSearchResult = { query: string; items: TSearchItem[]; }; export type SearchResetHandler = () => void; export const useAsyncSearch = ( list: TSearchItem[], getItemStr: SearchItemStrGetter, options?: UseAsyncSearchOptions ): [UseAsyncSearchResult | undefined, AsyncSearchHandler, SearchResetHandler] => { const [result, setResult] = useState>(); const [searchCallback, terminateSearch] = useMemo(() => { setResult(undefined); const handleMatch: MatchHandler = (item, query) => { const itemStr = getItemStr(item, query); 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); searchCallback(normalizedQuery); }, [searchCallback, options?.normalizeOptions] ); const resetHandler: SearchResetHandler = useCallback(() => { terminateSearch(); setResult(undefined); }, [terminateSearch]); useEffect( () => () => { // terminate any ongoing search request on unmount. terminateSearch(); }, [terminateSearch] ); return [result, searchHandler, resetHandler]; };