Tablecraft
API Reference

useInfiniteTable

Cursor-based infinite scroll powered by TanStack Query's useInfiniteQuery — accumulated pages, no pagination controls.

Cursor-based infinite scroll powered by TanStack Query's useInfiniteQuery. Pages accumulate into a flat list — no pagination controls needed. When sort or filter state changes, accumulated pages automatically reset to page 1 via query key composition.

Requires @tanstack/react-query:

npm i @tanstack/react-query

Signature

function useInfiniteTable<TData extends RowData, TCursor = unknown>(
  options: UseInfiniteTableOptions<TData, TCursor>
): UseInfiniteTableReturn<TData>

Options

OptionTypeDefaultDescription
queryKeyunknown[]requiredTanStack Query cache key
queryFn(context) => Promise<{ data: TData[], nextCursor?: TCursor }>requiredFetcher — return nextCursor: undefined to signal the last page
columnsColumnDef<TData, any>[]requiredColumn definitions
initialPageParamTCursor0First value passed as pageParam
sortingSortingOptions | booleantrueEnable sorting
globalFilterbooleantrueEnable global search
columnFiltersbooleantrueEnable column filters
rowSelectionRowSelectionOptions | booleanfalseEnable row selection
columnVisibilityColumnVisibilityOptions | booleanfalseEnable column visibility
groupingGroupingOptions | booleanfalseEnable row grouping
columnPinningColumnPinningOptions | booleanfalseEnable column pinning
staleTimenumberTanStack Query staleTime
gcTimenumberTanStack Query gcTime
enabledbooleanTanStack Query enabled

The queryFn context (InfiniteTableFnContext<TCursor>) receives:

PropertyType
pageParamTCursor
sortingSortingState
columnFiltersColumnFiltersState
globalFilterstring
groupingGroupingState

Return

PropertyTypeDescription
tableTable<TData>Full TanStack Table instance with all accumulated rows
loadMore() => voidFetch the next page
hasNextPagebooleantrue when nextCursor was returned from the last page
isFetchingNextPagebooleantrue while a loadMore call is in-flight
isLoadingbooleantrue on the very first fetch
isErrorbooleantrue if the query threw
errorError | nullThe thrown error, if any
refetch() => voidRe-run the query from the beginning
sortingSortingReturnSame shape as useTable
globalFilterGlobalFilterReturnSame shape as useTable
columnFiltersColumnFiltersReturnSame shape as useTable
rowSelectionRowSelectionReturnOpt-in — pass rowSelection: true
columnVisibilityColumnVisibilityReturnOpt-in — pass columnVisibility: true
groupingGroupingReturnOpt-in — pass grouping: true
columnPinningColumnPinningReturnOpt-in — pass columnPinning: true
emptyStateEmptyStateReturnSame shape as useTable

Usage

import { useInfiniteTable } from '@marvinackerman/tablecraft/query'

const { table, loadMore, hasNextPage, isFetchingNextPage, isLoading } = useInfiniteTable({
  queryKey: ['users'],
  initialPageParam: null as string | null,  // TCursor inferred as string | null
  queryFn: async ({ pageParam, sorting, globalFilter }) => {
    const res = await api.getUsers({
      cursor: pageParam,   // pageParam: string | null — fully typed
      sort: sorting,
      search: globalFilter,
    })
    return {
      data: res.data,
      nextCursor: res.nextCursor,   // string | null | undefined — typed
    }
  },
  columns,
  sorting: true,
  globalFilter: true,
})

<button onClick={loadMore} disabled={!hasNextPage || isFetchingNextPage}>
  {isFetchingNextPage ? 'Loading…' : 'Load more'}
</button>

All rows across every loaded page are available in table.getRowModel().rows as a single flat list — no page tracking needed.

Notes

  • Pair with useInfiniteScroll to trigger loadMore automatically instead of wiring a button.
  • For paginated (non-accumulating) server tables, use useQueryTable instead.

On this page