T
TanStack3y ago
fascinating-indigo

useInfiniteQuery not keeping all pages

Hello, I am using useInfiniteQuery for a paginated query. I expected when the second page is queried the pages array to have 2 pages, however it only contains the last page. I'm not sure what i'm doing incorrectly here.
export const playerKeys = {
all: ['players'] as const,
list: () => [...playerKeys.all, 'list'] as const,
detail: (id: string) => [...playerKeys.all, 'detail', id] as const,
};

export const usePlayers = (queryParams: PlayerSearchInputDTO = { page: 0 }) => {
const apiClient = useApiClient();

const queryOpts = useInfiniteQuery<PlayerOutputArrayDTOAPI, AxiosError<PlayerOutputArrayDTOAPI>>({
queryKey: [...playerKeys.list(), { ...queryParams }],
queryFn: async ({ pageParam = queryParams.page }) =>
(
await apiClient.player.playerControllerSearch({
...queryParams,
page: pageParam,
})
).data,
keepPreviousData: true,
getNextPageParam: (lastPage, pages) => hasNextPage(lastPage.meta, pages.length),
useErrorBoundary: (error) => error.response!.status >= 500,
});

const InfiniteScroll = useMemo(() => {
return <InfiniteScrollComponent {...queryOpts} />;
}, [queryOpts]);

return { ...queryOpts, InfiniteScroll };
};
export const playerKeys = {
all: ['players'] as const,
list: () => [...playerKeys.all, 'list'] as const,
detail: (id: string) => [...playerKeys.all, 'detail', id] as const,
};

export const usePlayers = (queryParams: PlayerSearchInputDTO = { page: 0 }) => {
const apiClient = useApiClient();

const queryOpts = useInfiniteQuery<PlayerOutputArrayDTOAPI, AxiosError<PlayerOutputArrayDTOAPI>>({
queryKey: [...playerKeys.list(), { ...queryParams }],
queryFn: async ({ pageParam = queryParams.page }) =>
(
await apiClient.player.playerControllerSearch({
...queryParams,
page: pageParam,
})
).data,
keepPreviousData: true,
getNextPageParam: (lastPage, pages) => hasNextPage(lastPage.meta, pages.length),
useErrorBoundary: (error) => error.response!.status >= 500,
});

const InfiniteScroll = useMemo(() => {
return <InfiniteScrollComponent {...queryOpts} />;
}, [queryOpts]);

return { ...queryOpts, InfiniteScroll };
};
No description
6 Replies
adverse-sapphire
adverse-sapphire3y ago
You're mixing infinite queries with pagination. Choose either or. With infinite queries, the page does not go into the query key. It always comes from getNextPageParam
fascinating-indigo
fascinating-indigoOP3y ago
if I use infiniteQueries how could I then start e.g. from page 4 lets say the page is coming from a url query parameter?
adverse-sapphire
adverse-sapphire3y ago
yeah like that:
queryFn: async ({ pageParam = queryParams.page }) =>
queryFn: async ({ pageParam = queryParams.page }) =>
but you don't put queryParams.page into the queryKey. Otherwise, you create a new cache entry for each page
fascinating-indigo
fascinating-indigoOP3y ago
I suppose I keep the other filter params in the queryKey?
export const usePlayers = ({ page, ...queryParams }: PlayerSearchInputDTO = { page: 0 }) => {
const apiClient = useApiClient();

const queryOpts = useInfiniteQuery<PlayerOutputArrayDTOAPI, AxiosError<PlayerOutputArrayDTOAPI>>({
queryKey: [...playerKeys.list(), { ...queryParams }],
queryFn: async ({ pageParam = page }) =>
(
await apiClient.player.playerControllerSearch({
...queryParams,
page: pageParam,
})
).data,
keepPreviousData: true,
getNextPageParam: (lastPage, pages) => hasNextPage(lastPage.meta, pages.length),
useErrorBoundary: (error) => error.response!.status >= 500,
});

const InfiniteScroll = useMemo(() => {
return <InfiniteScrollComponent {...queryOpts} />;
}, [queryOpts]);

return { ...queryOpts, InfiniteScroll };
};
export const usePlayers = ({ page, ...queryParams }: PlayerSearchInputDTO = { page: 0 }) => {
const apiClient = useApiClient();

const queryOpts = useInfiniteQuery<PlayerOutputArrayDTOAPI, AxiosError<PlayerOutputArrayDTOAPI>>({
queryKey: [...playerKeys.list(), { ...queryParams }],
queryFn: async ({ pageParam = page }) =>
(
await apiClient.player.playerControllerSearch({
...queryParams,
page: pageParam,
})
).data,
keepPreviousData: true,
getNextPageParam: (lastPage, pages) => hasNextPage(lastPage.meta, pages.length),
useErrorBoundary: (error) => error.response!.status >= 500,
});

const InfiniteScroll = useMemo(() => {
return <InfiniteScrollComponent {...queryOpts} />;
}, [queryOpts]);

return { ...queryOpts, InfiniteScroll };
};
also I have another question :) It is probably a bit more related to react table.. Hope you don't mind. It's currently erroring (I think) because the data.pages[pagination.paginationState.pageIndex]'s index does not exist yet in the data. should I use the pageParam here instead? Or how can I know pages has the new page to use the latest index

const { pagination, columnFilters, sorting, columnSearch, rowSelection } = useTableActions<PlayerOutputDTO>();

const { data, isLoading, isPreviousData } = usePlayers({
page: pagination.paginationState.pageIndex,
limit: pagination.paginationState.pageSize,
sortBy: sorting.sortingState[0]?.id,
sortDirection: sorting.sortingState[0]?.desc
? PlayerSearchInputDTOSortDirectionEnum.Desc
: PlayerSearchInputDTOSortDirectionEnum.Asc,
filters: {...},
search: {...},
});

...

if (isLoading || data === undefined) {
return <Loading />;
}

return (
<TableContainer>
<Table
id="players"
columns={columnDefs}
data={data.pages[pagination.paginationState.pageIndex].data}
rowSelection={rowSelection}
pagination={{
paginationState: pagination.paginationState,
setPaginationState: pagination.setPaginationState,
pageOptions: pagination.getPageOptions(data),
}}
columnFiltering={columnFilters}
columnSearch={columnSearch}
sorting={sorting}
/>
</TableContainer>
);
}

const { pagination, columnFilters, sorting, columnSearch, rowSelection } = useTableActions<PlayerOutputDTO>();

const { data, isLoading, isPreviousData } = usePlayers({
page: pagination.paginationState.pageIndex,
limit: pagination.paginationState.pageSize,
sortBy: sorting.sortingState[0]?.id,
sortDirection: sorting.sortingState[0]?.desc
? PlayerSearchInputDTOSortDirectionEnum.Desc
: PlayerSearchInputDTOSortDirectionEnum.Asc,
filters: {...},
search: {...},
});

...

if (isLoading || data === undefined) {
return <Loading />;
}

return (
<TableContainer>
<Table
id="players"
columns={columnDefs}
data={data.pages[pagination.paginationState.pageIndex].data}
rowSelection={rowSelection}
pagination={{
paginationState: pagination.paginationState,
setPaginationState: pagination.setPaginationState,
pageOptions: pagination.getPageOptions(data),
}}
columnFiltering={columnFilters}
columnSearch={columnSearch}
sorting={sorting}
/>
</TableContainer>
);
}
But I suppose, then pageParam will be undefined for the first page?
adverse-sapphire
adverse-sapphire3y ago
yes, keep other filter params. Yes, pageParam is undefined for the first page. Note that there is a breaking change around this for v5
fascinating-indigo
fascinating-indigoOP3y ago
Am I correct that infiniteQuery does not play well with react-table because you have to maintain 2 pagination states? I'm trying to create a single hook to use in combination with a table and in combination with infiniteScrolling. But I feel like the data requirements are too different. if I use useinfiniteQuery I have to maintain 2 pagination states If i use useQuery, the caching only has the latest page I suppose I could, useInfiniteQuery v5, and if the table pagination changes, also fetchNextPage with the table's pagination.PageIndex passing as prop to fetchNextPage? we decided on using different hooks for infiniteQuery together with the infiniteloader and one for the table environment.

Did you find this page helpful?