mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 23:59:45 +08:00
feat: enhance pagination support in API types and hooks
- Introduced new pagination types and interfaces, including `PaginationMode`, `BasePaginatedResponse`, `OffsetPaginatedResponse`, and `CursorPaginatedResponse`, to standardize pagination handling. - Updated `useDataApi` hook to support both offset and cursor-based pagination, improving data fetching capabilities. - Added `useInfiniteQuery` and `usePaginatedQuery` hooks for better management of paginated data, including loading states and pagination controls. - Refactored existing pagination logic to improve clarity and maintainability, ensuring consistent handling of pagination across the application.
This commit is contained in:
parent
24288cecf9
commit
b01113aae6
@ -134,15 +134,24 @@ import type { SerializedDataApiError } from './apiErrors'
|
|||||||
// Re-export for backwards compatibility in DataResponse
|
// Re-export for backwards compatibility in DataResponse
|
||||||
export type { SerializedDataApiError } from './apiErrors'
|
export type { SerializedDataApiError } from './apiErrors'
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Pagination Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination mode
|
||||||
|
*/
|
||||||
|
export type PaginationMode = 'offset' | 'cursor'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination parameters for list operations
|
* Pagination parameters for list operations
|
||||||
*/
|
*/
|
||||||
export interface PaginationParams {
|
export interface PaginationParams {
|
||||||
/** Page number (1-based) */
|
|
||||||
page?: number
|
|
||||||
/** Items per page */
|
/** Items per page */
|
||||||
limit?: number
|
limit?: number
|
||||||
/** Cursor for cursor-based pagination */
|
/** Page number (offset mode, 1-based) */
|
||||||
|
page?: number
|
||||||
|
/** Cursor (cursor mode) */
|
||||||
cursor?: string
|
cursor?: string
|
||||||
/** Sort field and direction */
|
/** Sort field and direction */
|
||||||
sort?: {
|
sort?: {
|
||||||
@ -152,14 +161,20 @@ export interface PaginationParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paginated response wrapper
|
* Base paginated response (shared fields)
|
||||||
*/
|
*/
|
||||||
export interface PaginatedResponse<T> {
|
export interface BasePaginatedResponse<T> {
|
||||||
/** Items for current page */
|
/** Items for current page */
|
||||||
items: T[]
|
items: T[]
|
||||||
/** Total number of items */
|
/** Total number of items */
|
||||||
total: number
|
total: number
|
||||||
/** Current page number */
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset-based paginated response
|
||||||
|
*/
|
||||||
|
export interface OffsetPaginatedResponse<T> extends BasePaginatedResponse<T> {
|
||||||
|
/** Current page number (1-based) */
|
||||||
page: number
|
page: number
|
||||||
/** Total number of pages */
|
/** Total number of pages */
|
||||||
pageCount: number
|
pageCount: number
|
||||||
@ -167,12 +182,37 @@ export interface PaginatedResponse<T> {
|
|||||||
hasNext: boolean
|
hasNext: boolean
|
||||||
/** Whether there are previous pages */
|
/** Whether there are previous pages */
|
||||||
hasPrev: boolean
|
hasPrev: boolean
|
||||||
/** Next cursor for cursor-based pagination */
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cursor-based paginated response
|
||||||
|
*/
|
||||||
|
export interface CursorPaginatedResponse<T> extends BasePaginatedResponse<T> {
|
||||||
|
/** Next cursor (undefined means no more data) */
|
||||||
nextCursor?: string
|
nextCursor?: string
|
||||||
/** Previous cursor for cursor-based pagination */
|
/** Previous cursor */
|
||||||
prevCursor?: string
|
prevCursor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unified paginated response (union type)
|
||||||
|
*/
|
||||||
|
export type PaginatedResponse<T> = OffsetPaginatedResponse<T> | CursorPaginatedResponse<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard: check if response is offset-based
|
||||||
|
*/
|
||||||
|
export function isOffsetPaginatedResponse<T>(response: PaginatedResponse<T>): response is OffsetPaginatedResponse<T> {
|
||||||
|
return 'page' in response && 'pageCount' in response
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard: check if response is cursor-based
|
||||||
|
*/
|
||||||
|
export function isCursorPaginatedResponse<T>(response: PaginatedResponse<T>): response is CursorPaginatedResponse<T> {
|
||||||
|
return 'nextCursor' in response || !('page' in response)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription options for real-time data updates
|
* Subscription options for real-time data updates
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import type { BodyForPath, QueryParamsForPath, ResponseForPath } from '@shared/data/api/apiPaths'
|
import type { BodyForPath, QueryParamsForPath, ResponseForPath } from '@shared/data/api/apiPaths'
|
||||||
import type { ConcreteApiPaths } from '@shared/data/api/apiTypes'
|
import type { ConcreteApiPaths, PaginationMode } from '@shared/data/api/apiTypes'
|
||||||
import type { PaginatedResponse } from '@shared/data/api/apiTypes'
|
import {
|
||||||
import { useState } from 'react'
|
isCursorPaginatedResponse,
|
||||||
|
type OffsetPaginatedResponse,
|
||||||
|
type PaginatedResponse
|
||||||
|
} from '@shared/data/api/apiTypes'
|
||||||
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import type { KeyedMutator } from 'swr'
|
import type { KeyedMutator } from 'swr'
|
||||||
import useSWR, { useSWRConfig } from 'swr'
|
import useSWR, { useSWRConfig } from 'swr'
|
||||||
|
import useSWRInfinite from 'swr/infinite'
|
||||||
import useSWRMutation from 'swr/mutation'
|
import useSWRMutation from 'swr/mutation'
|
||||||
|
|
||||||
import { dataApiService } from '../DataApiService'
|
import { dataApiService } from '../DataApiService'
|
||||||
@ -146,8 +151,10 @@ export function useQuery<TPath extends ConcreteApiPaths>(
|
|||||||
): {
|
): {
|
||||||
/** The fetched data */
|
/** The fetched data */
|
||||||
data?: ResponseForPath<TPath, 'GET'>
|
data?: ResponseForPath<TPath, 'GET'>
|
||||||
/** Loading state */
|
/** True during initial load (no data yet) */
|
||||||
loading: boolean
|
isLoading: boolean
|
||||||
|
/** True during any request (including background refresh) */
|
||||||
|
isRefreshing: boolean
|
||||||
/** Error if request failed */
|
/** Error if request failed */
|
||||||
error?: Error
|
error?: Error
|
||||||
/** Function to manually refetch data */
|
/** Function to manually refetch data */
|
||||||
@ -173,7 +180,8 @@ export function useQuery<TPath extends ConcreteApiPaths>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
loading: isLoading || isValidating,
|
isLoading,
|
||||||
|
isRefreshing: isValidating,
|
||||||
error: error as Error | undefined,
|
error: error as Error | undefined,
|
||||||
refetch,
|
refetch,
|
||||||
mutate
|
mutate
|
||||||
@ -275,7 +283,7 @@ export function useMutation<TPath extends ConcreteApiPaths, TMethod extends 'POS
|
|||||||
query?: QueryParamsForPath<TPath>
|
query?: QueryParamsForPath<TPath>
|
||||||
}) => Promise<ResponseForPath<TPath, TMethod>>
|
}) => Promise<ResponseForPath<TPath, TMethod>>
|
||||||
/** True while the mutation is in progress */
|
/** True while the mutation is in progress */
|
||||||
loading: boolean
|
isLoading: boolean
|
||||||
/** Error object if the mutation failed */
|
/** Error object if the mutation failed */
|
||||||
error: Error | undefined
|
error: Error | undefined
|
||||||
} {
|
} {
|
||||||
@ -367,7 +375,7 @@ export function useMutation<TPath extends ConcreteApiPaths, TMethod extends 'POS
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
mutate: options?.optimistic ? optimisticMutate : normalMutate,
|
mutate: options?.optimistic ? optimisticMutate : normalMutate,
|
||||||
loading: isMutating,
|
isLoading: isMutating,
|
||||||
error
|
error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -461,6 +469,242 @@ export function prefetch<TPath extends ConcreteApiPaths>(
|
|||||||
return apiFetcher(path, { query: options?.query as Record<string, any> })
|
return apiFetcher(path, { query: options?.query as Record<string, any> })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Infinite Query Hook
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for useInfiniteQuery hook
|
||||||
|
* SWR-related options are consolidated in swrOptions
|
||||||
|
*/
|
||||||
|
export interface UseInfiniteQueryOptions<TPath extends ConcreteApiPaths> {
|
||||||
|
/** Additional query parameters (excluding pagination params) */
|
||||||
|
query?: Omit<QueryParamsForPath<TPath>, 'page' | 'limit' | 'cursor'>
|
||||||
|
/** Items per page (default: 10) */
|
||||||
|
limit?: number
|
||||||
|
/** Pagination mode (default: 'cursor') */
|
||||||
|
mode?: PaginationMode
|
||||||
|
/** Whether to enable the query (default: true) */
|
||||||
|
enabled?: boolean
|
||||||
|
/** SWR options (including initialSize, revalidateAll, etc.) */
|
||||||
|
swrOptions?: Parameters<typeof useSWRInfinite>[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React hook for infinite scrolling data fetching
|
||||||
|
* Uses useSWRInfinite internally for efficient page management
|
||||||
|
*
|
||||||
|
* @template TPath - The concrete API path type
|
||||||
|
* @param path - API endpoint path that returns paginated data
|
||||||
|
* @param options - Configuration options for infinite query
|
||||||
|
* @returns Object containing accumulated items, loading states, and controls
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // Basic usage with cursor mode (default)
|
||||||
|
* const { items, hasNext, loadMore, isLoadingMore } = useInfiniteQuery('/test/items', {
|
||||||
|
* limit: 20,
|
||||||
|
* query: { search: 'hello' }
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Offset mode
|
||||||
|
* const { items, hasNext, loadMore } = useInfiniteQuery('/test/items', {
|
||||||
|
* mode: 'offset',
|
||||||
|
* limit: 20
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Custom SWR options
|
||||||
|
* const { items, hasNext, loadMore } = useInfiniteQuery('/test/items', {
|
||||||
|
* limit: 20,
|
||||||
|
* swrOptions: {
|
||||||
|
* initialSize: 2, // Load 2 pages initially
|
||||||
|
* revalidateFirstPage: false // Don't auto-refresh first page
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // With InfiniteScroll component
|
||||||
|
* <InfiniteScroll
|
||||||
|
* dataLength={items.length}
|
||||||
|
* next={loadMore}
|
||||||
|
* hasMore={hasNext}
|
||||||
|
* loader={<Spinner />}
|
||||||
|
* >
|
||||||
|
* {items.map(item => <ItemCard key={item.id} item={item} />)}
|
||||||
|
* </InfiniteScroll>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useInfiniteQuery<TPath extends ConcreteApiPaths>(
|
||||||
|
path: TPath,
|
||||||
|
options?: UseInfiniteQueryOptions<TPath>
|
||||||
|
): ResponseForPath<TPath, 'GET'> extends PaginatedResponse<infer T>
|
||||||
|
? {
|
||||||
|
/** Accumulated items from all loaded pages */
|
||||||
|
items: T[]
|
||||||
|
/** Raw page data array */
|
||||||
|
pages: PaginatedResponse<T>[]
|
||||||
|
/** Total number of items */
|
||||||
|
total: number
|
||||||
|
/** Number of pages loaded */
|
||||||
|
size: number
|
||||||
|
/** True during initial load (no data yet) */
|
||||||
|
isLoading: boolean
|
||||||
|
/** True during any request (including background refresh) */
|
||||||
|
isRefreshing: boolean
|
||||||
|
/** Error if request failed */
|
||||||
|
error?: Error
|
||||||
|
/** Whether there are more pages to load */
|
||||||
|
hasNext: boolean
|
||||||
|
/** Load the next page */
|
||||||
|
loadNext: () => void
|
||||||
|
/** Set number of pages to load */
|
||||||
|
setSize: (size: number | ((size: number) => number)) => void
|
||||||
|
/** Refresh all loaded pages */
|
||||||
|
refresh: () => void
|
||||||
|
/** Reset to first page only */
|
||||||
|
reset: () => void
|
||||||
|
/** SWR mutate function */
|
||||||
|
mutate: KeyedMutator<PaginatedResponse<T>[]>
|
||||||
|
}
|
||||||
|
: never {
|
||||||
|
const limit = options?.limit ?? 10
|
||||||
|
const mode = options?.mode ?? 'cursor' // Default: cursor mode
|
||||||
|
const enabled = options?.enabled !== false
|
||||||
|
|
||||||
|
// getKey: Generate SWR key for each page
|
||||||
|
const getKey = useCallback(
|
||||||
|
(pageIndex: number, previousPageData: PaginatedResponse<any> | null) => {
|
||||||
|
if (!enabled) return null
|
||||||
|
|
||||||
|
// Check if we've reached the end
|
||||||
|
if (previousPageData) {
|
||||||
|
if (mode === 'cursor') {
|
||||||
|
if (!isCursorPaginatedResponse(previousPageData) || !previousPageData.nextCursor) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// offset mode
|
||||||
|
if (isCursorPaginatedResponse(previousPageData)) {
|
||||||
|
// Response doesn't match expected mode
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!previousPageData.hasNext) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build pagination query
|
||||||
|
const paginationQuery: Record<string, any> = {
|
||||||
|
...(options?.query as Record<string, any>),
|
||||||
|
limit
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === 'cursor' && previousPageData && isCursorPaginatedResponse(previousPageData)) {
|
||||||
|
paginationQuery.cursor = previousPageData.nextCursor
|
||||||
|
} else if (mode === 'offset') {
|
||||||
|
paginationQuery.page = pageIndex + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return [path, paginationQuery] as [TPath, Record<string, any>]
|
||||||
|
},
|
||||||
|
[path, options?.query, limit, mode, enabled]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fetcher for infinite query - wraps getFetcher with proper types
|
||||||
|
const infiniteFetcher = (key: [ConcreteApiPaths, Record<string, any>?]) => {
|
||||||
|
return getFetcher(key) as Promise<PaginatedResponse<any>>
|
||||||
|
}
|
||||||
|
|
||||||
|
const swrResult = useSWRInfinite(getKey, infiniteFetcher, {
|
||||||
|
// Default configuration
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: true,
|
||||||
|
dedupingInterval: 5000,
|
||||||
|
errorRetryCount: 3,
|
||||||
|
errorRetryInterval: 1000,
|
||||||
|
initialSize: 1,
|
||||||
|
revalidateAll: false,
|
||||||
|
revalidateFirstPage: true,
|
||||||
|
parallel: false,
|
||||||
|
// User overrides
|
||||||
|
...options?.swrOptions
|
||||||
|
})
|
||||||
|
|
||||||
|
const { error, isLoading, isValidating, mutate, size, setSize } = swrResult
|
||||||
|
const data = swrResult.data as PaginatedResponse<any>[] | undefined
|
||||||
|
|
||||||
|
// Compute derived state
|
||||||
|
const items = useMemo(() => data?.flatMap((p) => p.items) ?? [], [data])
|
||||||
|
|
||||||
|
const hasNext = useMemo(() => {
|
||||||
|
if (!data?.length) return false
|
||||||
|
const last = data[data.length - 1]
|
||||||
|
if (mode === 'cursor') {
|
||||||
|
return isCursorPaginatedResponse(last) && !!last.nextCursor
|
||||||
|
}
|
||||||
|
return !isCursorPaginatedResponse(last) && (last as OffsetPaginatedResponse<any>).hasNext
|
||||||
|
}, [data, mode])
|
||||||
|
|
||||||
|
// Action methods
|
||||||
|
const loadNext = useCallback(() => {
|
||||||
|
if (!hasNext || isValidating) return
|
||||||
|
setSize((s) => s + 1)
|
||||||
|
}, [hasNext, isValidating, setSize])
|
||||||
|
|
||||||
|
const refresh = useCallback(() => mutate(), [mutate])
|
||||||
|
const reset = useCallback(() => setSize(1), [setSize])
|
||||||
|
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
pages: data ?? [],
|
||||||
|
total: data?.[0]?.total ?? 0,
|
||||||
|
size,
|
||||||
|
isLoading,
|
||||||
|
isRefreshing: isValidating,
|
||||||
|
error: error as Error | undefined,
|
||||||
|
hasNext,
|
||||||
|
loadNext,
|
||||||
|
setSize,
|
||||||
|
refresh,
|
||||||
|
reset,
|
||||||
|
mutate
|
||||||
|
} as unknown as ResponseForPath<TPath, 'GET'> extends PaginatedResponse<infer T>
|
||||||
|
? {
|
||||||
|
items: T[]
|
||||||
|
pages: PaginatedResponse<T>[]
|
||||||
|
total: number
|
||||||
|
size: number
|
||||||
|
isLoading: boolean
|
||||||
|
isRefreshing: boolean
|
||||||
|
error?: Error
|
||||||
|
hasNext: boolean
|
||||||
|
loadNext: () => void
|
||||||
|
setSize: (size: number | ((size: number) => number)) => void
|
||||||
|
refresh: () => void
|
||||||
|
reset: () => void
|
||||||
|
mutate: KeyedMutator<PaginatedResponse<T>[]>
|
||||||
|
}
|
||||||
|
: never
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Paginated Query Hook
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for usePaginatedQuery hook
|
||||||
|
*/
|
||||||
|
export interface UsePaginatedQueryOptions<TPath extends ConcreteApiPaths> {
|
||||||
|
/** Additional query parameters (excluding pagination params) */
|
||||||
|
query?: Omit<QueryParamsForPath<TPath>, 'page' | 'limit'>
|
||||||
|
/** Items per page (default: 10) */
|
||||||
|
limit?: number
|
||||||
|
/** Whether to enable the query (default: true) */
|
||||||
|
enabled?: boolean
|
||||||
|
/** SWR options */
|
||||||
|
swrOptions?: Parameters<typeof useSWR>[2]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React hook for paginated data fetching with type safety
|
* React hook for paginated data fetching with type safety
|
||||||
* Automatically manages pagination state and provides navigation controls
|
* Automatically manages pagination state and provides navigation controls
|
||||||
@ -482,7 +726,7 @@ export function prefetch<TPath extends ConcreteApiPaths>(
|
|||||||
* loading,
|
* loading,
|
||||||
* total,
|
* total,
|
||||||
* page,
|
* page,
|
||||||
* hasMore,
|
* hasNext,
|
||||||
* nextPage,
|
* nextPage,
|
||||||
* prevPage
|
* prevPage
|
||||||
* } = usePaginatedQuery('/test/items', {
|
* } = usePaginatedQuery('/test/items', {
|
||||||
@ -508,7 +752,7 @@ export function prefetch<TPath extends ConcreteApiPaths>(
|
|||||||
* Previous
|
* Previous
|
||||||
* </button>
|
* </button>
|
||||||
* <span>Page {page} of {Math.ceil(total / 20)}</span>
|
* <span>Page {page} of {Math.ceil(total / 20)}</span>
|
||||||
* <button onClick={nextPage} disabled={!hasMore}>
|
* <button onClick={nextPage} disabled={!hasNext}>
|
||||||
* Next
|
* Next
|
||||||
* </button>
|
* </button>
|
||||||
* </div>
|
* </div>
|
||||||
@ -537,12 +781,14 @@ export function usePaginatedQuery<TPath extends ConcreteApiPaths>(
|
|||||||
total: number
|
total: number
|
||||||
/** Current page number (1-based) */
|
/** Current page number (1-based) */
|
||||||
page: number
|
page: number
|
||||||
/** Loading state */
|
/** True during initial load (no data yet) */
|
||||||
loading: boolean
|
isLoading: boolean
|
||||||
|
/** True during any request (including background refresh) */
|
||||||
|
isRefreshing: boolean
|
||||||
/** Error if request failed */
|
/** Error if request failed */
|
||||||
error?: Error
|
error?: Error
|
||||||
/** Whether there are more pages available */
|
/** Whether there are more pages available */
|
||||||
hasMore: boolean
|
hasNext: boolean
|
||||||
/** Whether there are previous pages available */
|
/** Whether there are previous pages available */
|
||||||
hasPrev: boolean
|
hasPrev: boolean
|
||||||
/** Navigate to previous page */
|
/** Navigate to previous page */
|
||||||
@ -565,7 +811,7 @@ export function usePaginatedQuery<TPath extends ConcreteApiPaths>(
|
|||||||
limit
|
limit
|
||||||
} as Record<string, any>
|
} as Record<string, any>
|
||||||
|
|
||||||
const { data, loading, error, refetch } = useQuery(path, {
|
const { data, isLoading, isRefreshing, error, refetch } = useQuery(path, {
|
||||||
query: queryWithPagination as QueryParamsForPath<TPath>,
|
query: queryWithPagination as QueryParamsForPath<TPath>,
|
||||||
swrOptions: options?.swrOptions
|
swrOptions: options?.swrOptions
|
||||||
})
|
})
|
||||||
@ -576,11 +822,11 @@ export function usePaginatedQuery<TPath extends ConcreteApiPaths>(
|
|||||||
const total = paginatedData?.total || 0
|
const total = paginatedData?.total || 0
|
||||||
const totalPages = Math.ceil(total / limit)
|
const totalPages = Math.ceil(total / limit)
|
||||||
|
|
||||||
const hasMore = currentPage < totalPages
|
const hasNext = currentPage < totalPages
|
||||||
const hasPrev = currentPage > 1
|
const hasPrev = currentPage > 1
|
||||||
|
|
||||||
const nextPage = () => {
|
const nextPage = () => {
|
||||||
if (hasMore) {
|
if (hasNext) {
|
||||||
setCurrentPage((prev) => prev + 1)
|
setCurrentPage((prev) => prev + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -599,9 +845,10 @@ export function usePaginatedQuery<TPath extends ConcreteApiPaths>(
|
|||||||
items,
|
items,
|
||||||
total,
|
total,
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
loading,
|
isLoading,
|
||||||
|
isRefreshing,
|
||||||
error,
|
error,
|
||||||
hasMore,
|
hasNext,
|
||||||
hasPrev,
|
hasPrev,
|
||||||
prevPage,
|
prevPage,
|
||||||
nextPage,
|
nextPage,
|
||||||
@ -612,9 +859,10 @@ export function usePaginatedQuery<TPath extends ConcreteApiPaths>(
|
|||||||
items: T[]
|
items: T[]
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
loading: boolean
|
isLoading: boolean
|
||||||
|
isRefreshing: boolean
|
||||||
error?: Error
|
error?: Error
|
||||||
hasMore: boolean
|
hasNext: boolean
|
||||||
hasPrev: boolean
|
hasPrev: boolean
|
||||||
prevPage: () => void
|
prevPage: () => void
|
||||||
nextPage: () => void
|
nextPage: () => void
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user