-
-
Save nandorojo/c93f00c2a378264addfea3777174ccfe to your computer and use it in GitHub Desktop.
import { ConfigInterface, useSWRInfinite } from 'swr' | |
import { useMemo, useCallback, useRef } from 'react' | |
import last from 'lodash.last' | |
import get from 'lodash.get' | |
type PageKeyMaker<Page, Key extends any[]> = ( | |
index: number, | |
previousPageData?: Page | |
/** | |
* Mutable ref object. Set this to `true` before the request and `false` afterwards if the request is fetching more. | |
* | |
* For example, if the request has a `lastDocId`, it should set it to `true` before fetching. | |
* | |
* This prevents multiple page increases at once. | |
*/ | |
) => Key | |
type SWRInfiniteConfigInterface<Data = any, Error = any> = ConfigInterface< | |
Data[], | |
Error | |
> & { | |
initialSize?: number | |
revalidateAll?: boolean | |
persistSize?: boolean | |
} | |
export type UseGetInfinitePagesConfig< | |
Page extends object | |
> = SWRInfiniteConfigInterface<Page> & { | |
limit?: number | |
dataPath: keyof Page | string[] | |
} | |
type PageFetcher<Page, Key extends any[]> = ( | |
...params: Key | |
) => Page | Promise<Page> | |
const useGetInfinitePages = < | |
Page extends object, | |
Data, | |
/** | |
* Path to your list data | |
*/ | |
Key extends any[] = any[] | |
>( | |
key: PageKeyMaker<Page, Key>, | |
fetcher: PageFetcher<Page, Key>, | |
{ limit = 20, dataPath: path, ...options }: UseGetInfinitePagesConfig<Page> | |
) => { | |
const isFetching = useRef(false) | |
const dataPath = Array.isArray(path) ? path.join('.') : path | |
const { | |
data, | |
error, | |
isValidating, | |
mutate, | |
size, | |
setSize, | |
revalidate, | |
} = useSWRInfinite<Page>( | |
(index, previousPage) => { | |
const previousPageData = get(previousPage, dataPath) | |
// we've reached the last page, no more fetching | |
if (previousPageData?.length === 0) return null | |
// TODO is this correct? | |
// this means we haven't fetched the previous page yet, so don't fetch multiple at once. | |
// if (index > 0 && !previousPageData) return null | |
if (isFetching.current && index) return null | |
if (previousPageData && previousPageData.length < limit) { | |
return null | |
} | |
return key(index, previousPageData) | |
}, | |
async (...key: Key) => { | |
let val: Page | |
try { | |
isFetching.current = true | |
val = await fetcher(...key) | |
if (isFetching.current) { | |
isFetching.current = false | |
} | |
} catch (e) { | |
if (isFetching.current) { | |
isFetching.current = false | |
} | |
throw e | |
} | |
return val | |
}, | |
{ revalidateAll: false, ...options } | |
) | |
const firstPageData = get(data?.[0], dataPath) | |
const lastPage = last(data) | |
const lastPageData = get(lastPage, dataPath) | |
const canFetchMore = lastPageData?.length && lastPageData.length === limit | |
const isLoadingInitialData = !data && !error | |
const isLoadingMore = | |
isLoadingInitialData || | |
(isValidating && size > 1 && data && typeof data[size - 1] === 'undefined') | |
const isRefreshing = isValidating && data?.length === size | |
const isEmpty = firstPageData?.length === 0 | |
const fetchMore = useCallback(() => { | |
if (isLoadingMore || isFetching.current) return null | |
setSize((size) => { | |
console.log('🍔 [use-get-infinite-pages] is fetching more', { | |
currentPage: size, | |
}) | |
return size + 1 | |
}) | |
}, [isLoadingMore, setSize]) | |
const flat = useMemo( | |
() => | |
data | |
?.map((page) => get(page, dataPath) as Data) | |
?.flat(1) | |
.filter(Boolean) as | |
| (Data extends readonly (infer InnerArr)[] ? InnerArr : Data)[] | |
| undefined, | |
[data, dataPath] | |
) | |
return { | |
data: flat, | |
pages: data, | |
error, | |
isValidating, | |
mutate, | |
fetchMore, | |
isFetchingMore: !!isLoadingMore, | |
isRefreshing, | |
isEmpty, | |
isLoadingInitialData, | |
isLoadingMore, | |
lastPage, | |
size, | |
revalidate, | |
canFetchMore, | |
} | |
} | |
export default useGetInfinitePages |
It’s a bit outdated though I’ll update when I have time
have a hard time to figure this out using axios and type safety with typescript.
🍬 This is really sweet @nandorojo
🙌 Thanks!
To upgrade to [email protected]
(at time of writing it is at [email protected]
), I did the following:
imports
Remove:
import { ConfigInterface, useSWRInfinite } from 'swr'
Add:
import type { SWRInfiniteConfiguration } from 'swr/infinite'
import useSWRInfinite from 'swr/infinite'
types
Remove:
type SWRInfiniteConfigInterface ...
Edit:
export type UseGetInfinitePagesConfig<
Page extends object
> = SWRInfiniteConfiguration<Page> & {
limit?: number
dataPath: keyof Page | string[]
}
options defaults
revalidateAll: false
is out of the box (ref: swr
) so you could pass:
{ revalidateAll: false, ...options }
options
However, I like 👀 seeing 👀 it too, and have added revalidateFirstPage: false
on mine 😆
@bryantobing12 If you have not seen the updated swr
website there is an Axios section:
- https://swr.vercel.app/docs/data-fetching#axios
- https://swr.vercel.app/docs/pagination#useswrinfinite
To use with this gist, you should pass the fetcher
to the hook.
If there is more type safety required after you start hacking away, please share.
execute me, please show me example coding ..
Can you provide an code example?
Use it just like useSWRInfinite from SWR. It just comes with extra features.
https://swr.vercel.app/docs/pagination