function useAutoComplete()

in src/hooks/useAutoComplete/index.ts [48:165]


function useAutoComplete<T>({
  preFetch,
  url: rawUrl,
  params = {},
  input,
  parser,
  finder,
  searchEmpty = false,
  enabled = true,
}: AutoCompleteParameters<T>): { result: AutoCompleteResult; reset: () => void; refetch: () => void } {
  const qparams = new URLSearchParams({ ...DEFAULT_PARAMS, ...params }).toString();
  const requestUrl = apiHttp(`${rawUrl}${qparams ? '?' + qparams : ''}`);

  const [refetcher, setRefetcher] = useState(0);
  const [url] = useDebounce(requestUrl, preFetch ? 0 : 300);

  const [result, setResult] = useState<AutoCompleteResult>(
    DataStore[url] ? DataStore[url] : { status: 'NotAsked', data: [], timestamp: 0 },
  );

  const updateResult = useCallback(() => {
    const newResults = DataStore[url].data.filter((item) =>
      finder ? finder(item, input) : item.value.toLocaleLowerCase().includes(input.toLowerCase()),
    );
    setResult({
      status: DataStore[url]?.status || 'NotAsked',
      data: newResults,
      timestamp: DataStore[url]?.timestamp || 0,
    });
  }, [finder, input, url]);

  // Initialise caching
  useEffect(() => {
    if (!DataStore[url] && preFetch) {
      DataStore[url] = { status: 'NotAsked', data: [], timestamp: 0 };
    }
  }, [url, preFetch]);

  // Update results when input changes on prefetch
  useEffect(() => {
    if (input && preFetch) {
      updateResult();
    }
  }, [preFetch, input, updateResult]);

  const abortCtrl = useMemo(() => new AbortController(), []);

  useEffect(() => {
    if (DataStore[url] && DataStore[url].status === 'Loading') {
      abortCtrl.abort();
    }
  }, [requestUrl, abortCtrl, url]);

  const reset = useCallback(() => {
    setResult({ status: 'NotAsked', data: [], timestamp: 0 });
  }, []);

  const refetch = useCallback(() => {
    setRefetcher((num) => num + 1);
  }, []);

  useEffect(() => {
    if ((!searchEmpty && !input) || !enabled) return;

    const parseResult = parser || ((item: T) => ({ label: item, value: item }));

    if (preFetch) {
      if (DataStore[url]?.status !== 'NotAsked' && Date.now() - DataStore[url]?.timestamp < TIME_TO_REFETCH) {
        updateResult();
      } else {
        DataStore[url] = { status: 'Loading', data: [], timestamp: Date.now() };

        // fetch
        fetch(url, { signal: abortCtrl.signal })
          .then((resp) => resp.json())
          .then((response) => {
            if (Array.isArray(response) || Array.isArray(response.data)) {
              DataStore[url] = {
                status: 'Ok',
                timestamp: Date.now(),
                data: (Array.isArray(response) ? response : response.data).map(parseResult),
              };
            } else {
              DataStore[url] = { status: 'Error', data: [], timestamp: Date.now() };
            }
            updateResult();
          })
          .catch(() => {
            DataStore[url] = { status: 'Error', data: [], timestamp: Date.now() };
          });
      }
    } else {
      fetch(url, { signal: abortCtrl.signal })
        .then((resp) => resp.json())
        .then((response) => {
          if (response.status === 200 && Array.isArray(response.data)) {
            const data: AutoCompleteItem[] = response.data.map(parseResult);
            setResult({
              status: 'Ok',
              data: finder ? data.filter((item) => finder(item, input)) : data,
              timestamp: Date.now(),
            });
          } else {
            setResult({ status: 'Error', data: [], timestamp: Date.now() });
          }
        })
        .catch(() => {
          setResult({ status: 'Error', data: [], timestamp: Date.now() });
        });
    }
  }, [url, preFetch, searchEmpty, refetcher, abortCtrl, input, enabled, finder, parser, updateResult]);

  return {
    result,
    reset,
    refetch,
  };
}