Endless Scrolling
Endless scrolling — append mode — accumulates rows as the user reaches the end of the loaded list. New pages are concatenated onto what’s already loaded; there is no total count and no jump-to-row. The fetch function receives { cursor, limit, params, signal } and returns { rows, cursor, hasMore }.
APIs used
Section titled “APIs used”mode: 'append'— turns on append-mode bookkeepingdefaultAppendViewportHandler— built-in viewport handler that triggersloadMorenear the endmodel.send({ action: 'loadMore' })— manual load-more trigger
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'
import { defaultAppendViewportHandler, remoteModel } from '@virtuoso.dev/data-table'
import { fetchProducts } from './api'
import type { Product } from './api'
export default function App() {
const [model] = useState(() =>
remoteModel<Product>({
fetch: fetchProducts,
initialParams: {},
mode: 'append',
onViewportChange: defaultAppendViewportHandler,
pageSize: 20,
})
)
return (
<div className="space-y-4">
<Button onClick={() => model.send({ action: 'loadMore' })} variant="outline">
Load more
</Button>
<DataTable className="rounded-xl" model={model} style={{ height: 420 }}>
<DataTableColumn field="name">
<DataTableColumnHeader>Product</DataTableColumnHeader>
<DataTableCell className="font-medium">{({ row }) => row.data.name}</DataTableCell>
</DataTableColumn>
<DataTableColumn field="category">
<DataTableColumnHeader>Category</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
</DataTable>
</div>
)
}When this doesn’t fit
Section titled “When this doesn’t fit”Users can’t jump to row 50,000 without loading everything before it. For known total counts and arbitrary positioning, see the Server-side Pages example.