Server recipe
Remote windowed data
Keep a sparse host cache and populate it with visible-range fetch calls to /api/rows/window — not server-side rendering.
RemoteVirtualization50k rows
Interactive, production component
tsx
import { useCallback, useEffect, useRef, useState } from "react";
import { DataTable } from "../../Datatable";
import type { ColumnSetting } from "../../Datatable";
import {
beginRangeRequest,
cancelRangeRequest,
completeRangeRequest,
createRangeRequestTracker,
mergeSparseRows,
resetRangeRequestTracker,
} from "./serverWindowRequestCache";
import type { ExampleProps } from "./types";
type RemoteRow = {
__i: number;
sku: string;
product: string;
category: string;
stock: number;
};
type RemoteSort = { id: string | null; desc: boolean };
type WindowPayload = {
rows: RemoteRow[];
start: number;
total: number;
};
const ROW_COUNT = 50_000;
const FETCH_OVERSCAN = 80;
function ownerKeyFor(sort: RemoteSort, query: string) {
return JSON.stringify([sort.id, sort.desc, query.trim()]);
}
const columns: ColumnSetting<RemoteRow>[] = [
{ id: "__i", title: "Index", width: 90, align: "right", enableGlobalFilter: false },
{ id: "sku", title: "SKU", width: 140 },
{ id: "product", title: "Product", width: 230 },
{ id: "category", title: "Category", width: 140 },
{ id: "stock", title: "Stock", width: 110, align: "right", filterType: "number" },
];
export default function RemoteWindowExample({ theme }: ExampleProps) {
const cacheRef = useRef(new Map<number, RemoteRow>());
const trackerRef = useRef(createRangeRequestTracker());
const controllersRef = useRef(new Map<string, AbortController>());
const ownerKeyRef = useRef(ownerKeyFor({ id: null, desc: false }, ""));
const [range, setRange] = useState({ start: 0, end: 0 });
const [viewTotal, setViewTotal] = useState(ROW_COUNT);
const [dataVersion, setDataVersion] = useState(0);
const [sort, setSort] = useState<RemoteSort>({ id: null, desc: false });
const [query, setQuery] = useState("");
const [status, setStatus] = useState<"idle" | "loading" | "ready" | "error">("idle");
const getRow = useCallback(
(index: number) => cacheRef.current.get(index),
[],
);
const loadRange = useCallback(async (
start: number,
end: number,
nextSort: RemoteSort = sort,
nextQuery: string = query,
) => {
const visibleEnd = Math.max(end, start + 1);
const ownerKey = ownerKeyFor(nextSort, nextQuery);
if (ownerKey !== ownerKeyRef.current) return;
const requestId = beginRangeRequest(trackerRef.current, cacheRef.current, {
start,
end: visibleEnd,
ownerKey,
});
if (!requestId) return;
const controller = new AbortController();
controllersRef.current.set(requestId, controller);
const requestStart = Math.max(0, start - FETCH_OVERSCAN);
const requestEnd = Math.min(ROW_COUNT, visibleEnd + FETCH_OVERSCAN);
const params = new URLSearchParams({
start: String(requestStart),
limit: String(Math.max(1, requestEnd - requestStart)),
total: String(ROW_COUNT),
cols: "12",
});
if (nextSort.id) {
params.set("sortCol", nextSort.id);
params.set("sortDesc", nextSort.desc ? "1" : "0");
}
if (nextQuery.trim()) params.set("q", nextQuery.trim());
setStatus("loading");
try {
const response = await fetch(`/api/rows/window?${params}`, {
signal: controller.signal,
});
if (!response.ok) throw new Error("Window request failed");
const payload = await response.json() as WindowPayload;
if (controller.signal.aborted || ownerKey !== ownerKeyRef.current) {
cancelRangeRequest(trackerRef.current, requestId);
controllersRef.current.delete(requestId);
return;
}
const changed = mergeSparseRows(
cacheRef.current,
payload.start,
payload.rows,
);
completeRangeRequest(trackerRef.current, requestId);
controllersRef.current.delete(requestId);
setViewTotal(payload.total);
if (changed) setDataVersion((version) => version + 1);
setStatus(controllersRef.current.size ? "loading" : "ready");
} catch {
cancelRangeRequest(trackerRef.current, requestId);
controllersRef.current.delete(requestId);
if (controller.signal.aborted || ownerKey !== ownerKeyRef.current) return;
setStatus("error");
}
}, [query, sort]);
const resetOwnership = useCallback((nextOwnerKey: string) => {
if (nextOwnerKey === ownerKeyRef.current) return;
controllersRef.current.forEach((controller) => controller.abort());
controllersRef.current.clear();
resetRangeRequestTracker(trackerRef.current);
cacheRef.current.clear();
ownerKeyRef.current = nextOwnerKey;
setViewTotal(ROW_COUNT);
setStatus("idle");
}, []);
useEffect(() => () => {
controllersRef.current.forEach((controller) => controller.abort());
controllersRef.current.clear();
}, []);
const handleVisibleRange = useCallback((start: number, end: number) => {
setRange((current) =>
current.start === start && current.end === end ? current : { start, end },
);
void loadRange(start, end);
}, [loadRange]);
const handleSort = useCallback((nextSort: RemoteSort) => {
resetOwnership(ownerKeyFor(nextSort, query));
setSort(nextSort);
void loadRange(range.start, range.end, nextSort, query);
}, [loadRange, query, range, resetOwnership]);
const handleFilter = useCallback((nextQuery: string) => {
resetOwnership(ownerKeyFor(sort, nextQuery));
setQuery(nextQuery);
void loadRange(range.start, range.end, sort, nextQuery);
}, [loadRange, range, resetOwnership, sort]);
return (
<div>
<div className="example-demo-toolbar" aria-live="polite">
<span className="example-demo-toolbar__meta">
{range.start.toLocaleString()}–{range.end.toLocaleString()}
</span>
<span className="example-demo-toolbar__status">
{status === "error"
? "Request failed"
: status === "loading"
? "Loading…"
: `${viewTotal.toLocaleString()} rows`}
</span>
{status === "error" ? (
<div className="example-demo-toolbar__actions">
<button type="button" onClick={() => void loadRange(range.start, range.end)}>
Retry
</button>
</div>
) : null}
</div>
<DataTable
theme={theme}
title="Remote windowed catalog"
rowCount={viewTotal}
getRow={getRow}
dataVersion={dataVersion}
columnSettings={columns}
onVisibleRangeChange={handleVisibleRange}
externalSort={sort}
onSortChange={handleSort}
externalFilter={query}
onFilterChange={handleFilter}
showGlobalSearch
loading={status === "loading" && cacheRef.current.size === 0}
maxHeight={360}
enablePagination={false}
/>
</div>
);
}
Related API