Skip to content

Instantly share code, notes, and snippets.

@ChlorUpload
Created December 31, 2021 11:27
Show Gist options
  • Save ChlorUpload/7d8f9574206ce787e3a34370f30988a9 to your computer and use it in GitHub Desktop.
Save ChlorUpload/7d8f9574206ce787e3a34370f30988a9 to your computer and use it in GitHub Desktop.
useSearch
import { useCallback, useEffect, useMemo, useState } from "react";
import { useSearchParams } from "react-router-dom";
export type UseSearchRes<QueryT, ResultT> = {
query: QueryT;
results: ResultT[] | null;
loadMore: () => Promise<boolean>;
navigateSearch: (query: Partial<QueryT>) => void;
isSearching: boolean;
canLoadMore: boolean;
};
export type Query = {
query: string | undefined;
};
export type SearchResult<ResultT> = {
results: ResultT[];
nextUrl: string;
};
export function useSearch<QueryT extends Query, ResultT>(
customSearch: (query: QueryT) => Promise<SearchResult<ResultT> | null>,
customLoadMore: (nextUrl: string) => Promise<SearchResult<ResultT> | null>
): UseSearchRes<QueryT, ResultT> {
const [searchParams, setSearchParams] = useSearchParams();
const query = useMemo(
() => Object.fromEntries(searchParams.entries()) as QueryT,
[searchParams]
);
const [nextUrl, setNextUrl] = useState<string | null>(null);
const [results, setResults] = useState<ResultT[] | null>(null);
const [isSearching, setIsSearching] = useState(false);
const canLoadMore = useMemo(
() => nextUrl !== null && results !== null,
[nextUrl, results]
);
useEffect(() => {
(async () => {
setIsSearching(true);
setResults(null);
const res = await customSearch(query);
setIsSearching(false);
if (res) {
setNextUrl(res.nextUrl);
setResults(res.results);
} else {
// if search failed, go to default page
setSearchParams(new URLSearchParams({}), {
replace: true,
});
}
})();
//eslint-disable-next-line
}, [query]);
const loadMore = useCallback(async () => {
if (results === null) throw new Error("cannot load more. results is null.");
if (nextUrl === null)
throw new Error("cannot load more. next url is null.");
// fetch with nextUrl
const res = await customLoadMore(nextUrl);
if (res) {
const nextResults = [...results, ...res.results];
setResults(nextResults);
setNextUrl(res.nextUrl);
return true;
}
return false;
//eslint-disable-next-line
}, [nextUrl, results]);
const navigateSearch = (nextQuery: Partial<QueryT>) => {
const searchQuery = { ...(query as Partial<QueryT>), ...nextQuery };
setSearchParams(new URLSearchParams(Object.entries(searchQuery)), {
replace: true,
});
};
return {
query,
results,
loadMore,
navigateSearch,
isSearching,
canLoadMore,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment