import { useState, useRef, useCallback } from 'react' import type { AxiosInstance } from 'axios' import type { FetchState } from 'react-component-form' import type { CacheKey } from '../tools/cache' import { getPaginationCache, savePaginationCache } from '../tools/cache' export interface Query { [key: string]: string } export type NextPageAsync = (query?: Query) => Promise export type NextPage = (query?: Query, callback?: () => void) => void export interface UsePaginationOptions { api: AxiosInstance url: string inverse?: boolean cacheKey?: CacheKey } export interface PaginationItem { id: number } export type SetItems = React.Dispatch> export interface UsePaginationResult { items: T[] nextPage: NextPage resetPagination: () => void hasMore: boolean setItems: SetItems } export const usePagination = ( options: UsePaginationOptions ): UsePaginationResult => { const { api, url, inverse = false, cacheKey } = options const [items, setItems] = useState([]) const [hasMore, setHasMore] = useState(true) const fetchState = useRef('idle') const afterId = useRef(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( `${url}?${searchParameters.toString()}` ) if (!inverse) { const endIndex = newItems.length - 1 const lastItem = newItems[endIndex] afterId.current = newItems.length > 0 && lastItem != null ? lastItem.id : null } else { afterId.current = newItems.length > 0 && newItems[0] != null ? newItems[0].id : null } setItems((oldItems) => { const updatedItems = inverse ? [...newItems, ...oldItems] : [...oldItems, ...newItems] const unique = updatedItems.reduce((accumulator, item) => { const isExisting = accumulator.some((itemSome) => { return itemSome.id === item.id }) if (!isExisting) { accumulator.push(item) } return accumulator }, []) if (cacheKey != null) { savePaginationCache(cacheKey, unique) } return unique }) setHasMore(newItems.length > 0) fetchState.current = 'idle' }, [api, url, inverse, cacheKey] ) const nextPage: NextPage = useCallback( (query, callback) => { nextPageAsync(query) .then(() => { if (callback != null) { callback() } }) .catch((error) => { console.error(error) }) }, [nextPageAsync] ) const resetPagination = useCallback((): void => { if (cacheKey == null) { afterId.current = null setItems([]) } else { fetchState.current = 'loading' const newItems = getPaginationCache(cacheKey) setItems(newItems) if (!inverse) { const endIndex = newItems.length - 1 const lastItem = newItems[endIndex] afterId.current = newItems.length > 0 && lastItem != null ? lastItem.id : null } else { afterId.current = newItems.length > 0 && newItems[0] != null ? newItems[0].id : null } fetchState.current = 'idle' } }, [cacheKey, inverse]) return { items, hasMore, nextPage, resetPagination, setItems } }