Skip to content

Commit

Permalink
Add example using limit and offset
Browse files Browse the repository at this point in the history
- Add example using page and size
- add an intersection observer callback ref hook
  • Loading branch information
remus-selea authored and markerikson committed Dec 31, 2024
1 parent 337464d commit e1b53cf
Show file tree
Hide file tree
Showing 8 changed files with 507 additions and 42 deletions.
17 changes: 17 additions & 0 deletions examples/query/react/infinite-queries/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
InfiniteScrollAbout,
InfiniteScrollExample,
} from "./features/infinite-scroll/InfiniteScrollExample"
import LimitOffsetExample from "./features/limit-offset/LimitOffsetExample"
import { InfiniteScrollMaxPagesExample } from "./features/max-pages/InfiniteScrollMaxExample"
import PaginationInfScrollExample from "./features/pagination-infinite-scroll/PaginationInfScrollExample"
import { PaginationExample } from "./features/pagination/PaginationExample"

const Menu = () => {
Expand All @@ -31,6 +33,16 @@ const Menu = () => {
Bidirectional Cursor-Based Infinite Scroll
</Link>
</li>
<li>
<Link to="/examples/limit-offset">
Limit and Offset Infinite Scroll
</Link>
</li>
<li>
<Link to="/examples/pagination-infinite-scroll">
Pagination Infinite Scroll
</Link>
</li>
</ul>
</div>
)
Expand Down Expand Up @@ -67,6 +79,11 @@ const App = () => {
path="bidirectional-cursor-infinte-scroll"
element={<BidirectionalCursorInfScroll />}
/>
<Route path="limit-offset" element={<LimitOffsetExample />} />
<Route
path="pagination-infinite-scroll"
element={<PaginationInfScrollExample />}
/>
</Route>
</Routes>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useCallback, useRef } from "react"

export function useIntersectionCallback(onIntersectCallback: () => void) {
const intersectionObserverRef = useRef<IntersectionObserver | null>(null)

return useCallback(
(node: HTMLDivElement | null) => {
if (intersectionObserverRef.current) {
intersectionObserverRef.current.disconnect()
}

intersectionObserverRef.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
onIntersectCallback()
}
})

if (node) intersectionObserverRef.current.observe(node)
},
[onIntersectCallback],
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useRef, useState } from "react"
import { Link, useLocation } from "react-router"
import { useIntersectionCallback } from "../../app/useIntersectionCallback"
import { apiWithInfiniteScroll } from "./infiniteScrollApi"

const limit = 10
Expand Down Expand Up @@ -28,14 +29,8 @@ function BidirectionalCursorInfScroll({ startingProject = { id: 25 } }) {
},
)

const beforeRef = useIntersectionObserver({
isFetching,
callback: fetchPreviousPage,
})
const afterRef = useIntersectionObserver({
isFetching,
callback: fetchNextPage,
})
const beforeRef = useIntersectionCallback(fetchPreviousPage)
const afterRef = useIntersectionCallback(fetchNextPage)

const location = useLocation()

Expand Down Expand Up @@ -82,7 +77,7 @@ function BidirectionalCursorInfScroll({ startingProject = { id: 25 } }) {
height: "400px",
}}
>
<div ref={beforeRef}></div>
<div ref={beforeRef} />
{data?.pages.map(page => (
<React.Fragment key={page.pageInfo?.endCursor}>
{page.projects.map((project, index, arr) => {
Expand All @@ -109,7 +104,7 @@ function BidirectionalCursorInfScroll({ startingProject = { id: 25 } }) {
})}
</React.Fragment>
))}
<div ref={afterRef}></div>
<div ref={afterRef} />
</div>
<div>
<button
Expand Down Expand Up @@ -139,31 +134,3 @@ function BidirectionalCursorInfScroll({ startingProject = { id: 25 } }) {
}

export default BidirectionalCursorInfScroll

const useIntersectionObserver = ({
isFetching,
callback,
}: {
isFetching: boolean
callback: () => void
}) => {
const observerRef = useRef<IntersectionObserver | null>(null)

const observerCallback = useCallback(
(node: HTMLDivElement | null) => {
if (isFetching) return
if (observerRef.current) observerRef.current.disconnect()

observerRef.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
callback()
}
})

if (node) observerRef.current.observe(node)
},
[isFetching, callback],
)

return observerCallback
}
Original file line number Diff line number Diff line change
@@ -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 LimitOffsetExample() {
const {
combinedData,
hasPreviousPage,
hasNextPage,
// data,
error,
isFetching,
isLoading,
isError,
fetchNextPage,
fetchPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
} = apiWithInfiniteScroll.endpoints.projectsLimitOffset.useInfiniteQuery(
undefined,
{
selectFromResult: result => {
return {
...result,
combinedData: selectCombinedProjects(result),
}
},
},
)

const intersectionCallbackRef = useIntersectionCallback(fetchNextPage)
const location = useLocation()

return (
<div>
<h2>Limit and Offset Infinite Scroll</h2>
{isLoading ? (
<p>Loading...</p>
) : isError ? (
<span>Error: {error.message}</span>
) : null}

<>
<div>
<button
onClick={() => fetchPreviousPage()}
disabled={!hasPreviousPage || isFetchingPreviousPage}
>
{isFetchingPreviousPage
? "Loading more..."
: hasPreviousPage
? "Load Older"
: "Nothing more to load"}
</button>
</div>
<div
style={{
overflow: "auto",
margin: "1rem 0px",
height: "400px",
}}
>
{combinedData?.map((project, index, arr) => {
return (
<div
style={{
margin: "1em 0px",
border: "1px solid gray",
borderRadius: "5px",
padding: "2rem 1rem",
background: `hsla(${project.id * 30}, 60%, 80%, 1)`,
}}
key={project.id}
>
<div>
<div>{`Project ${project.id} (created at: ${project.createdAt})`}</div>
</div>
</div>
)
})}

<div ref={intersectionCallbackRef} />
</div>
<div>
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? "Loading more..."
: hasNextPage
? "Load Newer"
: "Nothing more to load"}
</button>
</div>
<div>
{isFetching && !isFetchingPreviousPage && !isFetchingNextPage
? "Background Updating..."
: null}
</div>
</>

<hr />
<Link to="/infinite-scroll/about" state={{ from: location.pathname }}>
Go to another page
</Link>
</div>
)
}

export default LimitOffsetExample
Original file line number Diff line number Diff line change
@@ -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",
}
},
}),
}),
})
Loading

0 comments on commit e1b53cf

Please sign in to comment.