useTableCache
useTableCache manages paginated, sorted data for VirtualTable. It fetches pages on demand as the user scrolls, integrates with React Suspense for the initial load, and provides upsert / remove methods for real-time updates without full re-fetches.
Import
Section titled “Import”import { useTableCache } from '@requence/table'Signature
Section titled “Signature”function useTableCache<T>( key: string, options: UseTableCacheOptions<T>,): TableCache<T>key — A stable string that identifies this cache. When the key changes (e.g. because a filter or sort order changed), the cache is discarded and the next render will re-suspend. Combine dynamic parameters into the key:
const cache = useTableCache(`users-${sortField}-${sortDir}-${filter}`, { // ...})UseTableCacheOptions
Section titled “UseTableCacheOptions”| Option | Type | Default | Description |
|---|---|---|---|
pageSize | number | — | Required. Number of rows per page. Controls how many items are fetched per request. |
getItemId | (item: T) => string | — | Required. Extracts a unique ID from an item. Used to match items during upsert and remove. |
compare | (a: T, b: T) => number | — | Required. Comparator for sort order. Return negative if a comes before b, positive if after, 0 if equal. Used by upsert() to binary-search the correct insertion position. |
fetchItems | (offset: number, limit: number) => Promise<{ items: T[]; total: number }> | — | Required. Fetches a page of data from the server. Must return the items for the requested range and the total count. See Suspense Behavior. |
fetchCount | () => Promise<number> | — | Optional. Fetches just the total count. Called (debounced, 150 ms) when an upsert arrives for an unknown ID. See fetchCount Debouncing. |
TableCache (Return Value)
Section titled “TableCache (Return Value)”| Property | Type | Description |
|---|---|---|
totalCount | number | Current total count. Updated by fetch results and by upsert/remove mutations. |
getItem | (index: number) => T | undefined | Returns the item at the given absolute index, or undefined if the page containing that index hasn’t been fetched yet. |
handleRangeChange | (range: { start: number; end: number }) => void | Pass this directly to VirtualTable’s onRangeChange prop. Triggers page fetches for any unfetched pages within the range. |
upsert | (item: T) => void | Insert or update an item. See Upsert Behavior. |
remove | (id: string) => void | Remove an item by ID. See Remove Behavior. |
reset | () => void | Clear all cached pages. See Reset Behavior. |
loading | boolean | true when a scroll-triggered page fetch is in-flight. false during the initial Suspense-suspended fetch. Use this to show a loading indicator in the header or footer. |
Suspense Behavior
Section titled “Suspense Behavior”useTableCache integrates with React Suspense to provide a loading state for the initial data fetch:
-
First fetch (no cached pages): The promise returned by
fetchItems(0, pageSize)is thrown, causing the component to suspend. Wrap the table in a<Suspense>boundary to show a fallback. -
Subsequent fetches (at least one page cached): Page fetches triggered by scrolling are non-blocking. The
loadingflag becomestrue, andgetItem()returnsundefinedfor indices on unfetched pages — causingBody.childrento returnnull, which renders skeleton rows.
<Suspense fallback={<TableSkeleton />}> <UsersTable /></Suspense>Upsert Behavior
Section titled “Upsert Behavior”upsert(item) handles three cases:
-
Item exists on a cached page — The item is updated in-place at its current position. No position change, no
totalCountchange. -
Item ID is known but on a non-cached page — The item is skipped. The cache has seen this ID in a previous fetch but the page has since been evicted or was never loaded. No action is taken because the server already has the correct data for that page.
-
Item ID is genuinely new — The item is inserted at the correct sorted position using the
comparefunction. Pages after the insertion point are invalidated (deleted from cache) because their indices have shifted.- If
fetchCountis provided, a debounced server call updatestotalCountauthoritatively. - If
fetchCountis not provided,totalCountis incremented optimistically.
- If
Remove Behavior
Section titled “Remove Behavior”remove(id) performs the following steps:
- The item’s ID is removed from tracking.
- All cached pages are searched for the item. If found, it is removed.
totalCountis decremented (clamped to 0).- All cached pages after the page where the item was found are invalidated, because their indices have shifted by one.
Reset Behavior
Section titled “Reset Behavior”reset() discards all cached data:
- Any pending
fetchCounttimer is cleared. - The internal cache iteration counter is incremented, effectively replacing the cache with a fresh, empty instance.
- The next render will re-suspend because no pages exist — the Suspense fallback is shown again while the first page is re-fetched.
Use reset() when the underlying data has changed in a way that can’t be expressed through upsert/remove (e.g. a bulk import, a filter change handled outside the key, or a manual refresh button).
fetchCount Debouncing
Section titled “fetchCount Debouncing”When fetchCount is provided and a genuinely new item arrives via upsert, the cache schedules a debounced call to fetchCount() with a 150 ms delay. If another upsert arrives within that window, the timer is reset. This prevents a burst of real-time events from triggering many redundant count queries.
The authoritative count from the server replaces the optimistic totalCount, correcting any drift caused by items that were inserted on non-cached pages.
If fetchCount is not provided, the cache falls back to incrementing totalCount by 1 for each new item. This is simpler but may drift if items are frequently inserted on pages the cache hasn’t loaded.