Created
December 31, 2021 11:27
-
-
Save ChlorUpload/7d8f9574206ce787e3a34370f30988a9 to your computer and use it in GitHub Desktop.
useSearch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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