diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-13 12:37:50 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-13 15:36:38 +0100 |
| commit | 599b70cd2390d08ce26ee44174b3f39c6587110c (patch) | |
| tree | 15d967fc142bf632df3dd55df373bfd4e33d2d90 /src/utils/hooks/use-pagination/use-pagination.ts | |
| parent | d7bcd93efcd4f1ae20678d0efa6777cfadc09a4e (diff) | |
refactor(hooks): rewrite usePagination hook
* replace `isLoadingInitialData` with `isLoading` & `isRefreshing`
* rename `fallbackData` prop to `fallback`
* replace `setSize` return with a `loadMore` callback
* add tests
Diffstat (limited to 'src/utils/hooks/use-pagination/use-pagination.ts')
| -rw-r--r-- | src/utils/hooks/use-pagination/use-pagination.ts | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/src/utils/hooks/use-pagination/use-pagination.ts b/src/utils/hooks/use-pagination/use-pagination.ts new file mode 100644 index 0000000..4df521b --- /dev/null +++ b/src/utils/hooks/use-pagination/use-pagination.ts @@ -0,0 +1,136 @@ +import { useCallback } from 'react'; +import useSWRInfinite, { type SWRInfiniteKeyLoader } from 'swr/infinite'; +import type { + EdgesResponse, + GraphQLEdgesInput, + Maybe, + Nullable, + Search, +} from '../../../types'; + +export type UsePaginationConfig<T> = { + /** + * The initial data. + */ + fallback?: EdgesResponse<T>[]; + /** + * A function to fetch more data. + */ + fetcher: (props: GraphQLEdgesInput & Search) => Promise<EdgesResponse<T>>; + /** + * The number of results per page. + */ + perPage: number; + /** + * An optional search string. + */ + searchQuery?: string; +}; + +export type UsePaginationReturn<T> = { + /** + * The data from the API. + */ + data: Maybe<EdgesResponse<T>[]>; + /** + * An error thrown by fetcher. + */ + error: unknown; + /** + * Determine if there's more data to fetch. + */ + hasNextPage: Maybe<boolean>; + /** + * Determine if there is some data. + */ + isEmpty: boolean; + /** + * Determine if there is some errors. + */ + isError: boolean; + /** + * Determine if there is an ongoing request and no loaded data. + */ + isLoading: boolean; + /** + * Determine if more data is currently loading. + */ + isLoadingMore: boolean; + /** + * Determine if the data is refreshing. + */ + isRefreshing: boolean; + /** + * Determine if there's a request or revalidation loading. + */ + isValidating: boolean; + /** + * A callback function to load more data. + */ + loadMore: () => Promise<void>; + /** + * Determine the number of pages that will be fetched and returned. + */ + size: number; +}; + +/** + * Handle data fetching with pagination. + * + * This hook is a wrapper of `useSWRInfinite` hook. + * + * @param {UsePaginationConfig<T>} props - The pagination configuration. + * @returns {UsePaginationReturn} An object with pagination data and helpers. + */ +export const usePagination = <T>({ + fallback, + fetcher, + perPage, + searchQuery, +}: UsePaginationConfig<T>): UsePaginationReturn<T> => { + const getKey: SWRInfiniteKeyLoader<EdgesResponse<T>> = useCallback( + (pageIndex, previousPageData): Nullable<GraphQLEdgesInput & Search> => { + if (previousPageData && !previousPageData.edges.length) return null; + + return { + first: perPage, + after: + pageIndex === 0 ? undefined : previousPageData?.pageInfo.endCursor, + search: searchQuery, + }; + }, + [perPage, searchQuery] + ); + + const { data, error, isLoading, isValidating, setSize, size } = + useSWRInfinite(getKey, fetcher, { fallbackData: fallback }); + + const loadMore = useCallback(async () => { + await setSize((prevSize) => prevSize + 1); + }, [setSize]); + + const hasNextPage = + data && data.length > 0 && data[data.length - 1].pageInfo.hasNextPage; + + const isLoadingMore = data + ? isLoading || (size > 0 && typeof data[size - 1] === 'undefined') + : false; + + const isEmpty = data?.[0]?.edges.length === 0; + + const isRefreshing = data ? isValidating && data.length === size : false; + + return { + data, + error, + hasNextPage, + isEmpty, + isError: !!error, + isLoading, + isLoadingMore, + isRefreshing, + isValidating, + loadMore, + size, + }; +}; |
