Infinite Scroll

Load more rows on demand with current ZenGrid infinite scrolling options.

ZenGrid infinite scrolling loads additional row batches when the user scrolls near the bottom of the loaded data. It is useful when you can append data incrementally and do not need a known total row count up front.

Basic Setup

infinite-scroll.ts
import { Grid, type ColumnDef } from '@zengrid/core';
import '@zengrid/core/dist/styles.css';
const columns: ColumnDef[] = [
{ field: 'id', header: 'ID', width: 80 },
{ field: 'name', header: 'Name', width: 180 },
{ field: 'email', header: 'Email', width: 240 },
];
async function loadUsers(offset: number, limit: number): Promise<any[][]> {
const response = await fetch(`/api/users?offset=${offset}&limit=${limit}`);
if (!response.ok) throw new Error('Failed to load users');
const result = await response.json();
return result.users.map((user: any) => [user.id, user.name, user.email]);
}
const firstPage = await loadUsers(0, 100);
const grid = new Grid(container, {
rowCount: firstPage.length,
colCount: columns.length,
rowHeight: 36,
colWidth: columns.map((column) => column.width ?? 140),
columns,
infiniteScrolling: {
enabled: true,
threshold: 20,
initialRowCount: firstPage.length,
},
onLoadMoreRows: async (currentRowCount) => {
return loadUsers(currentRowCount, 100);
},
});
grid.setData(firstPage);
grid.render();
Info

For known-total datasets with server-side sorting, filtering, and paging, backend dataMode plus onDataRequest is usually easier to reason about.

Callback Signature

load-more-signature.ts
type LoadMoreRowsCallback = (currentRowCount: number) => Promise<any[][]>;

The callback receives the current loaded row count. Return row arrays in the same column order as the grid.

Sliding Window

Use the sliding window options when loaded data can grow large enough to make memory usage matter.

sliding-window.ts
const grid = new Grid(container, {
rowCount: firstPage.length,
colCount: columns.length,
rowHeight: 36,
colWidth: columns.map((column) => column.width ?? 140),
columns,
infiniteScrolling: {
enabled: true,
threshold: 30,
initialRowCount: firstPage.length,
enableSlidingWindow: true,
windowSize: 1000,
pruneThreshold: 1200,
onDataPruned: (prunedRowCount, newVirtualOffset) => {
console.log({ prunedRowCount, newVirtualOffset });
},
},
onLoadMoreRows: (currentRowCount) => loadUsers(currentRowCount, 100),
});

pruneThreshold is a row-count threshold. When the in-memory window grows past it, older rows can be pruned according to the sliding-window strategy.

Stats And Reset

infinite-scroll-controls.ts
const stats = grid.getSlidingWindowStats();
console.log(stats.rowsInMemory, stats.totalRowsLoaded, stats.prunedRows);
grid.resetInfiniteScrolling();

Use resetInfiniteScrolling() after changing a search query or filter that affects the append stream.

Search Example

infinite-scroll-search.ts
let query = '';
async function loadSearchPage(offset: number): Promise<any[][]> {
const response = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, offset, limit: 100 }),
});
const result = await response.json();
return result.rows;
}
document.querySelector<HTMLInputElement>('#search')?.addEventListener('input', async (event) => {
query = event.currentTarget.value;
const firstPage = await loadSearchPage(0);
grid.setData(firstPage);
grid.resetInfiniteScrolling();
});

Production Notes

  • Handle network errors in onLoadMoreRows; returning an empty array is safer than throwing into the UI.
  • Debounce search inputs before resetting the stream.
  • Cache recently fetched pages server-side if users often scroll back through the same data.
  • Tune threshold, windowSize, and pruneThreshold with real row renderers, not synthetic rows only.