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<TablecraftFeatures, 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
staleTimenumber—TanStack Query staleTime
gcTimenumber—TanStack Query gcTime
enabledboolean—TanStack Query enabled

The queryFn context (InfiniteTableFnContext<TCursor>) receives:

PropertyType
pageParamTCursor
sortingSortingState
columnFiltersColumnFiltersState
globalFilterstring
groupingGroupingState

Return

PropertyTypeDescription
tableTablecraftTable<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.
  • useInfiniteTable does not get columnOrder, rowPinning or columnResizing — its return type is standalone rather than extending useTable's, so these three were left out of scope deliberately. If you need them, use useQueryTable or useTable instead, both of which support all three with the identical option shape.

On this page