perf(search): memoize virtual row to cut flushSync reconcile cost#1921
perf(search): memoize virtual row to cut flushSync reconcile cost#1921sanjams2 wants to merge 1 commit intohyperdxio:mainfrom
Conversation
== Motivation == Scroll on the search results table produces 90-450ms long tasks per tick == Details == @tanstack/react-virtual calls `flushSync(rerender)` on every scroll event while `isScrolling` is true (see the `onChange` adapter in the react-virtual source). With `overscan: 30` we keep ~90 rows mounted, and the inline `items.map()` render means React has no way to skip unchanged rows - it reconciles all 90 synchronously on every scroll notification, even when only 2-3 actually entered or left the window. The comparator keys on `row.id` (tanstack-table's stable identifier) rather than object identity, since `getRowModel()` may return fresh row references even when data is unchanged. `isHighlighted` and `isExpanded` are computed per-row in the parent so that selecting one row doesn't bust memo on the other 89. == Testing == - `npx jest DBRowTable.test.tsx` - 15/15 pass - DevTools Performance profile on search page: hot path was `scroll -> maybeNotify -> notify -> onChange -> flushSync -> React reconciler`; memoizing the row lets React bail out on unchanged ones
|
|
@sanjams2 is attempting to deploy a commit to the HyperDX Team on Vercel. A member of the Team first needs to authorize it. |
PR Review
|
🟡 Tier 3 — StandardIntroduces new logic, modifies core functionality, or touches areas with non-trivial risk. Why this tier:
Review process: Full human review — logic, architecture, edge cases. Stats
|
== Motivation ==
Scroll on the search results table produces 90-450ms long tasks per tick
== Details ==
@tanstack/react-virtual calls
flushSync(rerender)on every scroll event whileisScrollingis true (see theonChangeadapter in the react-virtual source). Withoverscan: 30we keep ~90 rows mounted, and the inlineitems.map()render means React has no way to skip unchanged rows - it reconciles all 90 synchronously on every scroll notification, even when only 2-3 actually entered or left the window.The comparator keys on
row.id(tanstack-table's stable identifier) rather than object identity, sincegetRowModel()may return fresh row references even when data is unchanged.isHighlightedandisExpandedare computed per-row in the parent so that selecting one row doesn't bust memo on the other 89.== Testing ==
npx jest DBRowTable.test.tsx- 15/15 passscroll -> maybeNotify -> notify -> onChange -> flushSync -> React reconciler; memoizing the row lets React bail out on unchanged ones