From 599b70cd2390d08ce26ee44174b3f39c6587110c Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Mon, 13 Nov 2023 12:37:50 +0100 Subject: refactor(hooks): rewrite usePagination hook * replace `isLoadingInitialData` with `isLoading` & `isRefreshing` * rename `fallbackData` prop to `fallback` * replace `setSize` return with a `loadMore` callback * add tests --- src/utils/hooks/use-pagination/use-pagination.ts | 136 +++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/utils/hooks/use-pagination/use-pagination.ts (limited to 'src/utils/hooks/use-pagination/use-pagination.ts') 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 = { + /** + * The initial data. + */ + fallback?: EdgesResponse[]; + /** + * A function to fetch more data. + */ + fetcher: (props: GraphQLEdgesInput & Search) => Promise>; + /** + * The number of results per page. + */ + perPage: number; + /** + * An optional search string. + */ + searchQuery?: string; +}; + +export type UsePaginationReturn = { + /** + * The data from the API. + */ + data: Maybe[]>; + /** + * An error thrown by fetcher. + */ + error: unknown; + /** + * Determine if there's more data to fetch. + */ + hasNextPage: Maybe; + /** + * 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; + /** + * 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} props - The pagination configuration. + * @returns {UsePaginationReturn} An object with pagination data and helpers. + */ +export const usePagination = ({ + fallback, + fetcher, + perPage, + searchQuery, +}: UsePaginationConfig): UsePaginationReturn => { + const getKey: SWRInfiniteKeyLoader> = useCallback( + (pageIndex, previousPageData): Nullable => { + 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, + }; +}; -- cgit v1.2.3