-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture useGridScrollSync
📝 Generated from
docs/architecture/use-grid-scroll-sync.md. Edit it there; changes made in the wiki are overwritten.
Internal hook. Manages all scroll-related state and the handleScroll callback for the grid viewport — including RAF-batched tick updates that trigger virtualization recomputes and the onRowsScrollEnd threshold check for infinite scroll.
File: lib/hooks/core/useGridScrollSync.ts
Before extraction, three pieces of scroll logic lived inline in DataGrid.tsx:
-
scrollPosRef/scrollRafRef/scrollTickstate setup (~5 lines) - A cleanup effect canceling the pending RAF on unmount (~4 lines)
-
handleScrollcallback with RAF scheduling and scroll-end threshold (~22 lines)
Extracting them gives the scroll logic its own test surface and removes 31 lines from DataGrid.tsx.
interface UseGridScrollSyncParams {
onRowsScrollEnd?: (params: GridRowScrollEndParams) => void;
/** Minimum overscan rows — the adaptive algorithm never goes below this. Defaults to 3. */
overscanRowCount?: number;
}interface UseGridScrollSyncResult {
scrollTop: number;
scrollLeft: number;
/** Dynamically computed overscan row count based on current scroll velocity. */
overscanRows: number;
handleScroll: (event: React.UIEvent<HTMLDivElement>) => void;
}| Return | Used by |
|---|---|
scrollTop |
useGridVirtualization — vertical scroll offset for computing the visible row window |
scrollLeft |
useGridVirtualization — horizontal scroll offset for computing the visible column window |
overscanRows |
useGridVirtualization — adaptive overscan count, set from current scroll velocity |
handleScroll |
Passed to the viewport <div onScroll={handleScroll}>
|
handleScroll fires on every scroll event (can be 60+ per second). Rather than recomputing the virtual window on every event, the hook schedules a single RAF per frame:
scroll event → measure velocity → cancel pending RAF → schedule new RAF
→ RAF fires → setScrollState({ scrollTop, scrollLeft, overscanRows })
All three values are bundled into a single setScrollState call so useGridVirtualization recomputes exactly once per frame. Scroll velocity (px/ms) is computed as deltaPos / deltaTime from the previous scroll event, then mapped to an overscan tier via velocityToOverscan():
| Velocity (px/ms) | overscanRows |
|---|---|
| < 0.5 | 3 |
| 0.5 – 3 | 5 |
| 3 – 15 | 12 |
| 15 – 40 | 20 |
| > 40 | 30 |
The state holds only the velocity-derived count (0 at rest); the returned value is Math.max(velocityCount, overscanRowCount), computed during render, so a new overscanRowCount applies immediately.
200 ms after the last scroll event, a setTimeout callback resets overscanRows back to overscanRowCount. This prevents the grid from holding a large overscan buffer while the user is idle.
When a vertical scroll brings the viewport within 100px of the bottom (scrollHeight - scrollTop - clientHeight < 100), the hook calls onRowsScrollEnd synchronously (not RAF-batched) so the consumer can load the next page immediately. A latch makes it fire once per arrival: it re-arms when the viewport leaves the threshold or rowCount changes. After every rowCount change an effect re-checks the viewport, so a page that does not fill it still fires (skipped with autoHeight or a 0px viewport). Horizontal-only scroll events are ignored.
attachViewport is the viewport's callback ref (combined by useGridViewport with useGridViewportSize, which attaches a ResizeObserver to every viewport element that mounts). When the viewport remounts (list view switched off again) it restores the last scroll position onto the new element; if the browser clamps it, the state is synced to the element.
The hook registers a single useEffect with an empty dependency array to cancel any pending RAF and decay timer on unmount:
useEffect(() => {
return () => {
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
if (decayRef.current !== null) clearTimeout(decayRef.current);
};
}, []);This prevents a setState call on an unmounted component if the grid is removed mid-scroll.
OpenGridX 3.2.2 · MIT · This wiki is generated from docs/ on every push to main. To fix a page, open a PR against the source file.
Start here
Components
- DataGrid
- Header
- Row
- Cell
- Toolbar
- Pagination
- Filter Panel
- Tooltip
- Column Visibility
- Column Grouping
- Column Resizing
- Empty State
- Error Overlay
- Aggregation Footer
Features
- Virtualization
- Filtering & Search
- Sorting & Pagination
- Custom Pagination
- Editing & Reordering
- Row Selection
- Clipboard
- Pinning
- State Persistence
- Aggregation & Pivot
- Tree Data & Grouping
- Cell Spanning
- Master-Detail
- Keyboard & Accessibility
- List View
- Infinite Scroll
- Data Source
- Loading States
- Toolbar Customization
- Export (CSV, Excel, JSON, Print)
- PDF Export
Customization
Upgrading
Contributing
- Contributing
- Testing
- Roadmap
- DataGrid orchestration
- GridRowMeta
- useGridControlledState
- useGridRowPipeline
- useGridColumns
- useGridVirtualization
- useGridVisibleRows
- useGridScrollSync
- useGridStateSnapshot