-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Hi Redux maintainers,
Very grateful for the new RTK Infinite Query API. I think this solves a lot of the pain points with manually merging paginated requests and cursor management. However, given that it's mainly built for infinite scrolling UIs, we've had to implement our own logic to make this API usable for tabular pagination.
The code snippet below highlights how we're trying to go about this. Almost every table in our app are currently server-side paginated and so we're trying to opt for a higher-order hook (hook factory) pattern to wrap the infinite query hook with all the extra components that our tables need.
However, there are some pain points when working with the types in the current version of the package. Namely, TypedUseInfiniteQuery
under generic conditions only returns:
refetch
fetchNextPage
fetchPreviousPage
I don't understand why data
for instance, could not just be returned as InfiniteData<ResultType, PageParam>
or why the booleans like isFetching
and hasNextPage
are not there.
Any advice on how to go about this? Perhaps this is the wrong pattern to use?
import {
type BaseQueryFn,
type InfiniteData,
type TypedUseInfiniteQuery,
} from '@reduxjs/toolkit/query/react';
import { useMemo, useRef, useState } from 'react';
type PaginationState = {
pageIndex: number;
pageSize: number;
};
export type CreatePaginatedQueryHookPageParam = {
limit?: number;
};
export const createPaginatedInfiniteQueryHook = <
ResultType,
QueryArg,
PageParam extends CreatePaginatedQueryHookPageParam,
BaseQuery extends BaseQueryFn,
>(
useInfiniteQuery: TypedUseInfiniteQuery<
ResultType,
QueryArg,
PageParam,
BaseQuery
>,
) => {
type UseInfiniteQueryParameters = Parameters<
TypedUseInfiniteQuery<ResultType, QueryArg, PageParam, BaseQuery>
>;
type UsePaginatedQueryArgs = UseInfiniteQueryParameters[0];
type UsePaginatedQuerySubscriptionOptions = Extract<
UseInfiniteQueryParameters[1],
object
>;
// this is the returned pagination hook
return (
queryArgs: UsePaginatedQueryArgs,
subscriptionOptions: UsePaginatedQuerySubscriptionOptions,
) => {
const { initialPageParam } = subscriptionOptions;
if (!initialPageParam?.limit) {
throw new Error(
'Paginated query hook requires initialPageParam with limit',
);
}
// client-side pagination state for components to display
const furthestPageIndex = useRef(0);
const [{ pageIndex, pageSize }, setPaginationState] =
useState<PaginationState>({
pageIndex: 0,
pageSize: initialPageParam.limit,
});
const { refetch, fetchNextPage, ...result } = useInfiniteQuery(
queryArgs,
subscriptionOptions,
);
/** Go forwards by 1 page. */
const getNextPage = async () => {
if (!result.hasNextPage) return;
if (pageIndex + 1 > furthestPageIndex.current) {
await fetchNextPage();
furthestPageIndex.current += 1;
}
setPaginationState(({ pageSize, pageIndex }) => ({
pageSize,
pageIndex: pageIndex + 1,
}));
};
/**
* Go back by 1 page.
*/
const getPrevPage = async () => {
if (pageIndex <= 0) return;
setPaginationState(({ pageSize, pageIndex }) => ({
pageSize,
pageIndex: pageIndex - 1,
}));
};
/**
* Changing the limit will always reset pagination to `pageIndex` 0.
*/
const changeLimitAndResetPagination = async (newLimit: number) => {
furthestPageIndex.current = 0;
setPaginationState({
pageIndex: 0,
pageSize: newLimit,
});
await refetch();
};
/**
* Consumers should call this when `QueryArgs/PageParams` for `useInfiniteQuery` changes.
*/
const refetchAndResetPagination = async () => {
furthestPageIndex.current = 0;
setPaginationState(({ pageSize }) => ({
pageIndex: 0,
pageSize: pageSize,
}));
await refetch();
};
// we have to typecast data here
const data =
'data' in result
? (result.data as InfiniteData<ResultType, PageParam>)
: undefined;
const currentPage = useMemo(() => {
if (data) {
return data.pages[pageIndex];
}
}, [pageIndex, data]);
return {
getNextPage,
getPrevPage,
changeLimitAndResetPagination,
refetchAndResetPagination,
currentPage,
paginationState: {
pageIndex,
pageSize,
canGetNextPage: result.hasNextPage as boolean,
canGetPrevPage: pageIndex > 0,
},
infiniteQueryResults: result,
};
};
};