1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
import { useCallback } from 'react';
import useSWRInfinite, { type SWRInfiniteKeyLoader } from 'swr/infinite';
import type {
GraphQLConnection,
GraphQLEdgesInput,
Maybe,
Nullable,
} from '../../../types';
export type UsePaginationFetcherInput = GraphQLEdgesInput & {
search?: string;
};
export type UsePaginationConfig<T> = Pick<GraphQLEdgesInput, 'after'> & {
/**
* The initial data.
*/
fallback?: GraphQLConnection<T>[];
/**
* A function to fetch more data.
*/
fetcher: (props: UsePaginationFetcherInput) => Promise<GraphQLConnection<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<GraphQLConnection<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>({
after,
fallback,
fetcher,
perPage,
searchQuery,
}: UsePaginationConfig<T>): UsePaginationReturn<T> => {
const getKey: SWRInfiniteKeyLoader<GraphQLConnection<T>> = useCallback(
(pageIndex, previousPageData): Nullable<UsePaginationFetcherInput> => {
if (previousPageData && !previousPageData.edges.length) return null;
return {
first: perPage,
after: pageIndex === 0 ? after : previousPageData?.pageInfo.endCursor,
search: searchQuery,
};
},
[after, 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,
};
};
|