Shadcn React Table

Search documentation

Search the docs

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.

Infinite scroll
GitHub
Loaded 20 of 200
ID
AvaThompsonOwnerEngineeringactive22$45,000Jan 2, 2023
0%
LiamNguyenAdminMarketinginactive25$47,137Jan 13, 2023
9%
NoahSilvaEditorDesignpending28$49,274Jan 24, 2023
18%
EmmaCarterViewerSalesactive31$51,411Feb 4, 2023
27%
OliviaRossiOwnerSupportinactive34$53,548Feb 15, 2023
36%
WilliamWalkerAdminEngineeringpending37$55,685Feb 26, 2023
45%
SophiaPatelEditorMarketingactive40$57,822Mar 9, 2023
54%
JamesMullerViewerDesigninactive43$59,959Mar 20, 2023
63%
IsabellaParkOwnerSalespending46$62,096Mar 31, 2023
72%
LucasReyesAdminSupportactive49$64,233Apr 11, 2023
81%
MiaJohanssonEditorEngineeringinactive52$66,370Apr 22, 2023
90%
BenjaminCostaViewerMarketingpending55$68,507May 3, 2023
99%
CharlotteChenOwnerDesignactive58$70,644May 14, 2023
7%
HenryCohenAdminSalesinactive61$72,781May 25, 2023
16%
AmeliaNovakEditorSupportpending24$74,918Jun 5, 2023
25%
JackDiazViewerEngineeringactive27$77,055Jun 16, 2023
34%
HarperDuboisOwnerMarketinginactive30$79,192Jun 27, 2023
43%
LeoHaddadAdminDesignpending33$81,329Jul 8, 2023
52%
EllaKhanEditorSalesactive36$83,466Jul 19, 2023
61%
MasonTanakaViewerSupportinactive39$85,603Jul 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

PropTypeDefaultDescription
enableInfiniteScroll?booleanfalseLoad 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?() => voidCalled 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?booleanfalseWhether more rows remain to be loaded. Default false.
isFetchingNextPage?booleanfalseWhether a load is currently in flight (blocks re-triggering). Default false.
infiniteScrollThreshold?number200Distance in px from the bottom at which to prefetch. Default 200.