2022-01-01 20:42:25 +01:00
|
|
|
import { useState, useRef, useCallback } from 'react'
|
2022-08-31 21:44:33 +02:00
|
|
|
import type { AxiosInstance } from 'axios'
|
2022-08-28 18:26:56 +02:00
|
|
|
import type { FetchState } from 'react-component-form'
|
2022-01-01 20:42:25 +01:00
|
|
|
|
2022-08-31 21:44:33 +02:00
|
|
|
import type { CacheKey } from '../tools/cache'
|
|
|
|
import { getPaginationCache, savePaginationCache } from '../tools/cache'
|
2022-01-01 20:42:25 +01:00
|
|
|
|
|
|
|
export interface Query {
|
|
|
|
[key: string]: string
|
|
|
|
}
|
|
|
|
export type NextPageAsync = (query?: Query) => Promise<void>
|
|
|
|
export type NextPage = (query?: Query, callback?: () => void) => void
|
|
|
|
|
|
|
|
export interface UsePaginationOptions {
|
|
|
|
api: AxiosInstance
|
|
|
|
url: string
|
|
|
|
inverse?: boolean
|
2022-08-24 17:22:55 +02:00
|
|
|
cacheKey?: CacheKey
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface PaginationItem {
|
|
|
|
id: number
|
2022-01-01 20:42:25 +01:00
|
|
|
}
|
|
|
|
|
2022-01-13 18:21:45 +01:00
|
|
|
export type SetItems<T> = React.Dispatch<React.SetStateAction<T[]>>
|
|
|
|
|
2022-01-01 20:42:25 +01:00
|
|
|
export interface UsePaginationResult<T> {
|
|
|
|
items: T[]
|
|
|
|
nextPage: NextPage
|
|
|
|
resetPagination: () => void
|
|
|
|
hasMore: boolean
|
2022-01-13 18:21:45 +01:00
|
|
|
setItems: SetItems<T>
|
2022-01-01 20:42:25 +01:00
|
|
|
}
|
|
|
|
|
2022-08-24 17:22:55 +02:00
|
|
|
export const usePagination = <T extends PaginationItem>(
|
2022-01-01 20:42:25 +01:00
|
|
|
options: UsePaginationOptions
|
|
|
|
): UsePaginationResult<T> => {
|
2022-08-24 17:22:55 +02:00
|
|
|
const { api, url, inverse = false, cacheKey } = options
|
2022-01-01 20:42:25 +01:00
|
|
|
|
|
|
|
const [items, setItems] = useState<T[]>([])
|
|
|
|
const [hasMore, setHasMore] = useState(true)
|
|
|
|
const fetchState = useRef<FetchState>('idle')
|
|
|
|
const afterId = useRef<number | null>(null)
|
|
|
|
|
|
|
|
const nextPageAsync: NextPageAsync = useCallback(
|
|
|
|
async (query) => {
|
|
|
|
if (fetchState.current !== 'idle') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fetchState.current = 'loading'
|
|
|
|
const searchParameters = new URLSearchParams(query)
|
|
|
|
searchParameters.append('limit', '20')
|
|
|
|
if (afterId.current != null) {
|
|
|
|
searchParameters.append('after', afterId.current.toString())
|
|
|
|
}
|
|
|
|
const { data: newItems } = await api.get<T[]>(
|
|
|
|
`${url}?${searchParameters.toString()}`
|
|
|
|
)
|
2022-08-30 21:30:06 +02:00
|
|
|
if (!inverse) {
|
|
|
|
afterId.current =
|
|
|
|
newItems.length > 0 ? newItems[newItems.length - 1].id : null
|
|
|
|
} else {
|
|
|
|
afterId.current = newItems.length > 0 ? newItems[0].id : null
|
|
|
|
}
|
2022-01-01 20:42:25 +01:00
|
|
|
setItems((oldItems) => {
|
2022-08-24 17:22:55 +02:00
|
|
|
const updatedItems = inverse
|
|
|
|
? [...newItems, ...oldItems]
|
|
|
|
: [...oldItems, ...newItems]
|
2022-08-30 21:30:06 +02:00
|
|
|
const unique = updatedItems.reduce<T[]>((accumulator, item) => {
|
2022-08-31 21:44:33 +02:00
|
|
|
const isExisting = accumulator.some((itemSome) => {
|
|
|
|
return itemSome.id === item.id
|
|
|
|
})
|
2022-08-30 21:30:06 +02:00
|
|
|
if (!isExisting) {
|
|
|
|
accumulator.push(item)
|
|
|
|
}
|
|
|
|
return accumulator
|
|
|
|
}, [])
|
2022-08-24 17:22:55 +02:00
|
|
|
if (cacheKey != null) {
|
2022-08-30 21:30:06 +02:00
|
|
|
savePaginationCache(cacheKey, unique)
|
2022-08-24 17:22:55 +02:00
|
|
|
}
|
2022-08-30 21:30:06 +02:00
|
|
|
return unique
|
2022-01-01 20:42:25 +01:00
|
|
|
})
|
|
|
|
setHasMore(newItems.length > 0)
|
|
|
|
fetchState.current = 'idle'
|
|
|
|
},
|
2022-08-24 17:22:55 +02:00
|
|
|
[api, url, inverse, cacheKey]
|
2022-01-01 20:42:25 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const nextPage: NextPage = useCallback(
|
|
|
|
(query, callback) => {
|
|
|
|
nextPageAsync(query)
|
|
|
|
.then(() => {
|
|
|
|
if (callback != null) {
|
|
|
|
callback()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error(error)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
[nextPageAsync]
|
|
|
|
)
|
|
|
|
|
|
|
|
const resetPagination = useCallback((): void => {
|
2022-08-24 17:22:55 +02:00
|
|
|
if (cacheKey == null) {
|
|
|
|
afterId.current = null
|
|
|
|
setItems([])
|
|
|
|
} else {
|
2022-08-30 21:30:06 +02:00
|
|
|
fetchState.current = 'loading'
|
2022-08-24 17:22:55 +02:00
|
|
|
const newItems = getPaginationCache<T>(cacheKey)
|
|
|
|
setItems(newItems)
|
2022-08-30 21:30:06 +02:00
|
|
|
if (!inverse) {
|
|
|
|
afterId.current =
|
|
|
|
newItems.length > 0 ? newItems[newItems.length - 1].id : null
|
|
|
|
} else {
|
|
|
|
afterId.current = newItems.length > 0 ? newItems[0].id : null
|
|
|
|
}
|
|
|
|
fetchState.current = 'idle'
|
2022-08-24 17:22:55 +02:00
|
|
|
}
|
2022-08-30 21:30:06 +02:00
|
|
|
}, [cacheKey, inverse])
|
2022-01-01 20:42:25 +01:00
|
|
|
|
2022-01-13 18:21:45 +01:00
|
|
|
return { items, hasMore, nextPage, resetPagination, setItems }
|
2022-01-01 20:42:25 +01:00
|
|
|
}
|