Infinite scroll
Infinite scroll loads more rows as the user scrolls toward the bottom, instead of paging through the data. Set enableInfiniteScroll: true and drive it with three controlled props: onLoadMore, hasNextPage, and isFetchingNextPage.
ID | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Ava | Thompson | Owner | Engineering | active | 22 | $45,000 | Jan 2, 2023 | 0% | ||
| Liam | Nguyen | Admin | Marketing | inactive | 25 | $47,137 | Jan 13, 2023 | 9% | ||
| Noah | Silva | Editor | Design | pending | 28 | $49,274 | Jan 24, 2023 | 18% | ||
| Emma | Carter | Viewer | Sales | active | 31 | $51,411 | Feb 4, 2023 | 27% | ||
| Olivia | Rossi | Owner | Support | inactive | 34 | $53,548 | Feb 15, 2023 | 36% | ||
| William | Walker | Admin | Engineering | pending | 37 | $55,685 | Feb 26, 2023 | 45% | ||
| Sophia | Patel | Editor | Marketing | active | 40 | $57,822 | Mar 9, 2023 | 54% | ||
| James | Muller | Viewer | Design | inactive | 43 | $59,959 | Mar 20, 2023 | 63% | ||
| Isabella | Park | Owner | Sales | pending | 46 | $62,096 | Mar 31, 2023 | 72% | ||
| Lucas | Reyes | Admin | Support | active | 49 | $64,233 | Apr 11, 2023 | 81% | ||
| Mia | Johansson | Editor | Engineering | inactive | 52 | $66,370 | Apr 22, 2023 | 90% | ||
| Benjamin | Costa | Viewer | Marketing | pending | 55 | $68,507 | May 3, 2023 | 99% | ||
| Charlotte | Chen | Owner | Design | active | 58 | $70,644 | May 14, 2023 | 7% | ||
| Henry | Cohen | Admin | Sales | inactive | 61 | $72,781 | May 25, 2023 | 16% | ||
| Amelia | Novak | Editor | Support | pending | 24 | $74,918 | Jun 5, 2023 | 25% | ||
| Jack | Diaz | Viewer | Engineering | active | 27 | $77,055 | Jun 16, 2023 | 34% | ||
| Harper | Dubois | Owner | Marketing | inactive | 30 | $79,192 | Jun 27, 2023 | 43% | ||
| Leo | Haddad | Admin | Design | pending | 33 | $81,329 | Jul 8, 2023 | 52% | ||
| Ella | Khan | Editor | Sales | active | 36 | $83,466 | Jul 19, 2023 | 61% | ||
| Mason | Tanaka | Viewer | Support | inactive | 39 | $85,603 | Jul 30, 2023 | 70% | ||
| $1,306,030 |
When infinite scroll is on, the pager is hidden and pagination is forced to a single growing page, so every appended row renders. The table watches a sentinel near the bottom of the scroll surface and calls onLoadMore only when the sentinel nears the viewport and hasNextPage is true and a fetch is not already in flight.
Controlled fetch state
You own the data and the loading flags; the table only decides when to ask for more. Append the next chunk to data in your onLoadMore handler.
import { DataTable, useDataTable } from "@/components/ui/data-table"
function Table() {
const [rows, setRows] = React.useState(() => firstPage)
const [isFetchingNextPage, setIsFetchingNextPage] = React.useState(false)
const hasNextPage = rows.length < total
// Memoize so the table doesn't re-trigger on every render.
const loadMore = React.useCallback(() => {
setIsFetchingNextPage(true)
fetchNextPage(rows.length).then((next) => {
setRows((prev) => [...prev, ...next])
setIsFetchingNextPage(false)
})
}, [rows.length])
const table = useDataTable({
data: rows,
columns,
getRowId: (row) => row.id,
enableInfiniteScroll: true,
hasNextPage,
isFetchingNextPage,
onLoadMore: loadMore,
})
return <DataTable table={table} />
}
Pairs with TanStack Query
The contract maps one-to-one onto TanStack Query's useInfiniteQuery: pass
fetchNextPage as onLoadMore, and forward hasNextPage /
isFetchingNextPage straight through.
Prefetch distance
infiniteScrollThreshold (default 200) is the distance in pixels from the bottom at which the next chunk is requested, so data arrives before the user hits the end.
const table = useDataTable({
data: rows,
columns,
enableInfiniteScroll: true,
hasNextPage,
isFetchingNextPage,
onLoadMore: loadMore,
infiniteScrollThreshold: 400,
})
Works with or without virtualization
The sentinel sits after the rendered window, so infinite scroll works whether or not row virtualization is on. For very large loaded datasets, pair it with enableRowVirtualization so only the visible rows render.
const table = useDataTable({
data: rows,
columns,
enableInfiniteScroll: true,
enableRowVirtualization: true,
hasNextPage,
isFetchingNextPage,
onLoadMore: loadMore,
})
Reset on sort / filter changes
In server mode, resetting the query (on a new sort or filter) is your job:
replace data with the first page again. The sentinel re-observes the new
rows automatically.
Relevant options
| Prop | Type | Default | Description |
|---|---|---|---|
enableInfiniteScroll? | boolean | false | Load more rows as the user scrolls near the bottom (infinite append). When on, the pager is hidden and pagination is forced to a single growing page, so all appended rows render. Fully controlled: the table calls `onLoadMore` only when the bottom sentinel nears the viewport AND `hasNextPage` AND `!isFetchingNextPage`. Pairs directly with TanStack Query's `useInfiniteQuery` (`fetchNextPage`/`hasNextPage`/`isFetchingNextPage`). Default false. |
onLoadMore? | () => void | — | Called to request the next chunk. Should be stable (memoized) so the table doesn't re-trigger on every render. Append the result to `data`. |
hasNextPage? | boolean | false | Whether more rows remain to be loaded. Default false. |
isFetchingNextPage? | boolean | false | Whether a load is currently in flight (blocks re-triggering). Default false. |
infiniteScrollThreshold? | number | 200 | Distance in px from the bottom at which to prefetch. Default 200. |