+
Limit and Offset Infinite Scroll
+ {isLoading ? (
+
Loading...
+ ) : isError ? (
+
Error: {error.message}
+ ) : null}
+
+ <>
+
+
+
+
+ {combinedData?.map((project, index, arr) => {
+ return (
+
+
+
{`Project ${project.id} (created at: ${project.createdAt})`}
+
+
+ )
+ })}
+
+
+
+
+
+
+
+ {isFetching && !isFetchingPreviousPage && !isFetchingNextPage
+ ? "Background Updating..."
+ : null}
+
+ >
+
+
+
+ Go to another page
+
+
+ )
+}
+
+export default LimitOffsetExample
diff --git a/examples/query/react/infinite-queries/src/features/limit-offset/infiniteScrollApi.ts b/examples/query/react/infinite-queries/src/features/limit-offset/infiniteScrollApi.ts
new file mode 100644
index 0000000000..27bd2998a7
--- /dev/null
+++ b/examples/query/react/infinite-queries/src/features/limit-offset/infiniteScrollApi.ts
@@ -0,0 +1,72 @@
+import { baseApi } from "../baseApi"
+
+type Project = {
+ id: number
+ createdAt: string
+}
+
+export type ProjectsResponse = {
+ projects: Project[]
+ numFound: number
+ serverTime: string
+}
+
+interface ProjectsInitialPageParam {
+ offset: number
+ limit: number
+}
+
+export const apiWithInfiniteScroll = baseApi.injectEndpoints({
+ endpoints: builder => ({
+ projectsLimitOffset: builder.infiniteQuery<
+ ProjectsResponse,
+ void,
+ ProjectsInitialPageParam
+ >({
+ infiniteQueryOptions: {
+ initialPageParam: {
+ offset: 0,
+ limit: 20,
+ },
+ getNextPageParam: (
+ lastPage,
+ allPages,
+ lastPageParam,
+ allPageParams,
+ ) => {
+ const nextOffset = lastPageParam.offset + lastPageParam.limit
+ const remainingItems = lastPage?.numFound - nextOffset
+
+ if (remainingItems <= 0) {
+ return undefined
+ }
+
+ return {
+ ...lastPageParam,
+ offset: nextOffset,
+ }
+ },
+ getPreviousPageParam: (
+ firstPage,
+ allPages,
+ firstPageParam,
+ allPageParams,
+ ) => {
+ const prevOffset = firstPageParam.offset - firstPageParam.limit
+ if (prevOffset < 0) return undefined
+
+ return {
+ ...firstPageParam,
+ offset: firstPageParam.offset - firstPageParam.limit,
+ }
+ },
+ },
+ query: ({ offset, limit }) => {
+ return {
+ url: `https://example.com/api/projectsLimitOffset?offset=${offset}&limit=${limit}`,
+ method: "GET",
+ }
+ },
+ }),
+ }),
+})
diff --git a/examples/query/react/infinite-queries/src/features/pagination-infinite-scroll/PaginationInfScrollExample.tsx b/examples/query/react/infinite-queries/src/features/pagination-infinite-scroll/PaginationInfScrollExample.tsx
new file mode 100644
index 0000000000..c461b0a09c
--- /dev/null
+++ b/examples/query/react/infinite-queries/src/features/pagination-infinite-scroll/PaginationInfScrollExample.tsx
@@ -0,0 +1,129 @@
+import { createSelector } from "@reduxjs/toolkit"
+import {
+ BaseQueryFn,
+ TypedUseQueryStateResult,
+} from "@reduxjs/toolkit/query/react"
+import { Link, useLocation } from "react-router"
+import { useIntersectionCallback } from "../../app/useIntersectionCallback"
+import { apiWithInfiniteScroll, ProjectsResponse } from "./infiniteScrollApi"
+
+type ProjectsInfiniteQueryResult = TypedUseQueryStateResult<
+ { pages: ProjectsResponse[] },
+ unknown,
+ BaseQueryFn
+>
+
+const selectCombinedProjects = createSelector(
+ (res: ProjectsInfiniteQueryResult) => {
+ return res.data
+ },
+ data => data?.pages?.map(item => item?.projects)?.flat(),
+)
+
+function PaginationInfScrollExample() {
+ const {
+ combinedData,
+ hasPreviousPage,
+ hasNextPage,
+ // data,
+ error,
+ isFetching,
+ isLoading,
+ isError,
+ fetchNextPage,
+ fetchPreviousPage,
+ isFetchingNextPage,
+ isFetchingPreviousPage,
+ } = apiWithInfiniteScroll.endpoints.projectsPaginated.useInfiniteQuery(
+ undefined,
+ {
+ selectFromResult: result => {
+ return {
+ ...result,
+ combinedData: selectCombinedProjects(result),
+ }
+ },
+ },
+ )
+
+ const intersectionCallbackRef = useIntersectionCallback(fetchNextPage)
+ const location = useLocation()
+
+ return (
+
+
Pagination Infinite Scroll
+ {isLoading ? (
+
Loading...
+ ) : isError ? (
+
Error: {error.message}
+ ) : null}
+
+ <>
+
+
+
+
+ {combinedData?.map((project, index, arr) => {
+ return (
+
+
+
{`Project ${project.id} (created at: ${project.createdAt})`}
+
+
+ )
+ })}
+
+
+
+
+
+
+
+ {isFetching && !isFetchingPreviousPage && !isFetchingNextPage
+ ? "Background Updating..."
+ : null}
+
+ >
+
+
+
+ Go to another page
+
+
+ )
+}
+
+export default PaginationInfScrollExample
diff --git a/examples/query/react/infinite-queries/src/features/pagination-infinite-scroll/infiniteScrollApi.ts b/examples/query/react/infinite-queries/src/features/pagination-infinite-scroll/infiniteScrollApi.ts
new file mode 100644
index 0000000000..c519dce5bb
--- /dev/null
+++ b/examples/query/react/infinite-queries/src/features/pagination-infinite-scroll/infiniteScrollApi.ts
@@ -0,0 +1,72 @@
+import { baseApi } from "../baseApi"
+
+type Project = {
+ id: number
+ createdAt: string
+}
+
+export type ProjectsResponse = {
+ projects: Project[]
+ serverTime: string
+ totalPages: number
+}
+
+interface ProjectsInitialPageParam {
+ page: number
+ size: number
+}
+
+export const apiWithInfiniteScroll = baseApi.injectEndpoints({
+ endpoints: builder => ({
+ projectsPaginated: builder.infiniteQuery<
+ ProjectsResponse,
+ void,
+ ProjectsInitialPageParam
+ >({
+ infiniteQueryOptions: {
+ initialPageParam: {
+ page: 0,
+ size: 20,
+ },
+ getNextPageParam: (
+ lastPage,
+ allPages,
+ lastPageParam,
+ allPageParams,
+ ) => {
+ const nextPage = lastPageParam.page + 1
+ const remainingPages = lastPage?.totalPages - nextPage
+
+ if (remainingPages <= 0) {
+ return undefined
+ }
+
+ return {
+ ...lastPageParam,
+ page: nextPage,
+ }
+ },
+ getPreviousPageParam: (
+ firstPage,
+ allPages,
+ firstPageParam,
+ allPageParams,
+ ) => {
+ const prevPage = firstPageParam.page - 1
+ if (prevPage < 0) return undefined
+
+ return {
+ ...firstPageParam,
+ page: prevPage,
+ }
+ },
+ },
+ query: ({ page, size }) => {
+ return {
+ url: `https://example.com/api/projectsPaginated?page=${page}&size=${size}`,
+ method: "GET",
+ }
+ },
+ }),
+ }),
+})
diff --git a/examples/query/react/infinite-queries/src/mocks/handlers.ts b/examples/query/react/infinite-queries/src/mocks/handlers.ts
index e205f63709..f6d747268b 100644
--- a/examples/query/react/infinite-queries/src/mocks/handlers.ts
+++ b/examples/query/react/infinite-queries/src/mocks/handlers.ts
@@ -134,12 +134,9 @@ export const handlers = [
const hasPreviousPage = startCursor !== 0
await delay(1000)
-
- const serverTime = Date.now()
-
return HttpResponse.json({
projects: resultProjects,
- serverTime,
+ serverTime: Date.now(),
pageInfo: {
startCursor,
endCursor,
@@ -154,4 +151,64 @@ export const handlers = [
}
},
),
+ http.get(
+ "https://example.com/api/projectsLimitOffset",
+ async ({ request }) => {
+ const url = new URL(request.url)
+ const limit = parseInt(url.searchParams.get("limit") ?? "5", 10)
+ let offset = parseInt(url.searchParams.get("offset") ?? "0", 10)
+
+ if (isNaN(offset) || offset < 0) {
+ offset = 0
+ }
+ if (isNaN(limit) || limit <= 0) {
+ return HttpResponse.json(
+ {
+ message:
+ "Invalid 'limit' parameter. It must be a positive integer.",
+ },
+ { status: 400 },
+ )
+ }
+
+ const result = projects.slice(offset, offset + limit)
+
+ await delay(1000)
+ return HttpResponse.json({
+ projects: result,
+ serverTime: Date.now(),
+ numFound: projects.length,
+ })
+ },
+ ),
+ http.get("https://example.com/api/projectsPaginated", async ({ request }) => {
+ const url = new URL(request.url)
+ const size = parseInt(url.searchParams.get("size") ?? "5", 10)
+ let page = parseInt(url.searchParams.get("page") ?? "0", 10)
+
+ if (isNaN(page) || page < 0) {
+ page = 0
+ }
+ if (isNaN(size) || size <= 0) {
+ return HttpResponse.json(
+ { message: "Invalid 'size' parameter. It must be a positive integer." },
+ { status: 400 },
+ )
+ }
+
+ const startIndex = page * size
+ const endIndex = startIndex + size
+ const result = projects.slice(startIndex, endIndex)
+
+ await delay(1000)
+ return HttpResponse.json({
+ projects: result,
+ serverTime: Date.now(),
+ totalPages: Math.ceil(projects.length / size), // totalPages is a parameter required for this example, but an API could include additional fields that can be used in certain scenarios, such as determining getNextPageParam or getPreviousPageParam.
+ // totalElements: projects.length,
+ // numberOfElements: result.length,
+ // isLast: endIndex >= projects.length,
+ // isFirst: page === 0,
+ })
+ }),
]