import React from 'react'
import ReactDOM from 'react-dom'
import {
QueryClient,
QueryClientProvider,
useInfiniteQuery,
} from '@tanstack/react-query'
import './index.css'
import { useVirtualizer } from '@tanstack/react-virtual'
const queryClient = new QueryClient()
async function fetchServerPage(
limit: number,
offset: number = 0,
): Promise<{ rows: Array<string>; nextOffset: number }> {
const rows = new Array(limit)
.fill(0)
.map((_, i) => `Async loaded row #${i + offset * limit}`)
await new Promise((r) => setTimeout(r, 500))
return { rows, nextOffset: offset + 1 }
}
function App() {
const {
status,
data,
error,
isFetching,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: (ctx) => fetchServerPage(10, ctx.pageParam),
getNextPageParam: (lastGroup) => lastGroup.nextOffset,
initialPageParam: 0,
})
const allRows = data ? data.pages.flatMap((d) => d.rows) : []
const parentRef = React.useRef<HTMLDivElement>(null)
const rowVirtualizer = useVirtualizer({
count: hasNextPage ? allRows.length + 1 : allRows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 100,
overscan: 5,
})
React.useEffect(() => {
const [lastItem] = [...rowVirtualizer.getVirtualItems()].reverse()
if (!lastItem) {
return
}
if (
lastItem.index >= allRows.length - 1 &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage()
}
}, [
hasNextPage,
fetchNextPage,
allRows.length,
isFetchingNextPage,
rowVirtualizer.getVirtualItems(),
])
return (
<div>
<p>
This infinite scroll example uses React Query's useInfiniteScroll hook
to fetch infinite data from a posts endpoint and then a rowVirtualizer
is used along with a loader-row placed at the bottom of the list to
trigger the next page to load.
</p>
<br />
<br />
{status === 'pending' ? (
<p>Loading...</p>
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
<div
ref={parentRef}
className="List"
style={{
height: `500px`,
width: `100%`,
overflow: 'auto',
}}
>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const isLoaderRow = virtualRow.index > allRows.length - 1
const post = allRows[virtualRow.index]
return (
<div
key={virtualRow.index}
className={
virtualRow.index % 2 ? 'ListItemOdd' : 'ListItemEven'
}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{isLoaderRow
? hasNextPage
? 'Loading more...'
: 'Nothing more to load'
: post}
</div>
)
})}
</div>
</div>
)}
<div>
{isFetching && !isFetchingNextPage ? 'Background Updating...' : null}
</div>
<br />
<br />
{process.env.NODE_ENV === 'development' ? (
<p>
<strong>Notice:</strong> You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
</p>
) : null}
</div>
)
}
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root'),
)
import React from 'react'
import ReactDOM from 'react-dom'
import {
QueryClient,
QueryClientProvider,
useInfiniteQuery,
} from '@tanstack/react-query'
import './index.css'
import { useVirtualizer } from '@tanstack/react-virtual'
const queryClient = new QueryClient()
async function fetchServerPage(
limit: number,
offset: number = 0,
): Promise<{ rows: Array<string>; nextOffset: number }> {
const rows = new Array(limit)
.fill(0)
.map((_, i) => `Async loaded row #${i + offset * limit}`)
await new Promise((r) => setTimeout(r, 500))
return { rows, nextOffset: offset + 1 }
}
function App() {
const {
status,
data,
error,
isFetching,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: (ctx) => fetchServerPage(10, ctx.pageParam),
getNextPageParam: (lastGroup) => lastGroup.nextOffset,
initialPageParam: 0,
})
const allRows = data ? data.pages.flatMap((d) => d.rows) : []
const parentRef = React.useRef<HTMLDivElement>(null)
const rowVirtualizer = useVirtualizer({
count: hasNextPage ? allRows.length + 1 : allRows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 100,
overscan: 5,
})
React.useEffect(() => {
const [lastItem] = [...rowVirtualizer.getVirtualItems()].reverse()
if (!lastItem) {
return
}
if (
lastItem.index >= allRows.length - 1 &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage()
}
}, [
hasNextPage,
fetchNextPage,
allRows.length,
isFetchingNextPage,
rowVirtualizer.getVirtualItems(),
])
return (
<div>
<p>
This infinite scroll example uses React Query's useInfiniteScroll hook
to fetch infinite data from a posts endpoint and then a rowVirtualizer
is used along with a loader-row placed at the bottom of the list to
trigger the next page to load.
</p>
<br />
<br />
{status === 'pending' ? (
<p>Loading...</p>
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
<div
ref={parentRef}
className="List"
style={{
height: `500px`,
width: `100%`,
overflow: 'auto',
}}
>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const isLoaderRow = virtualRow.index > allRows.length - 1
const post = allRows[virtualRow.index]
return (
<div
key={virtualRow.index}
className={
virtualRow.index % 2 ? 'ListItemOdd' : 'ListItemEven'
}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{isLoaderRow
? hasNextPage
? 'Loading more...'
: 'Nothing more to load'
: post}
</div>
)
})}
</div>
</div>
)}
<div>
{isFetching && !isFetchingNextPage ? 'Background Updating...' : null}
</div>
<br />
<br />
{process.env.NODE_ENV === 'development' ? (
<p>
<strong>Notice:</strong> You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
</p>
) : null}
</div>
)
}
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root'),
)
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.