Skip to content

Implement a virtualised infinite list for the key holder table that renders only visible rows and supports 10,000+ holders without layout thrash #757

Description

@Chucks1093

Summary

The key holder table currently renders all rows in the DOM. A creator with thousands of holders causes severe layout thrash, high memory usage, and janky scrolling. This issue implements a fully virtualised list that renders only the rows visible in the viewport plus a small overscan buffer, recycles DOM nodes as the user scrolls, and maintains smooth 60fps scrolling for lists of 10,000+ holders.

Scope

1. Virtual list engine (no external library)

  • Implement a useVirtualList hook accepting { itemCount, itemHeight, containerHeight, overscan }
  • Returns { startIndex, endIndex, offsetY, totalHeight } updated on every scroll event
  • Use IntersectionObserver to detect when the list container enters and exits the viewport and pause scroll event listeners when off-screen
  • Scroll event listener must be passive and throttled to one update per requestAnimationFrame

2. Fixed-height row rendering

  • Each holder row has a fixed height of 48px
  • The list container has position: relative with height: totalHeight
  • Visible rows are absolutely positioned with top: index * itemHeight
  • Only endIndex - startIndex + 2 * overscan DOM nodes exist at any time regardless of total holder count

3. Cursor-based data fetching

  • Fetch holders in pages of 50 from the key holder list endpoint using React Query's useInfiniteQuery
  • When endIndex approaches within 20 rows of the last fetched item, trigger the next page fetch
  • While a page is loading, render skeleton rows in the overscan zone
  • Maintain a flat Map<index, HolderRow> cache so fetched rows are never re-fetched during the same session

4. Dynamic rank and share recalculation

  • When a new page is loaded and the total holder count changes, recompute ranks and share percentages for all already-loaded rows without re-fetching them
  • Recomputation must complete in under 5ms for 10,000 rows (use a typed Float64Array for share percentages)

5. Scroll restoration

  • Store the scroll offset in sessionStorage keyed by holder-list:{creatorWallet}
  • On mount, restore the scroll offset and pre-fetch the page containing the restored index before rendering

6. Performance and unit tests

  • Performance test: render a 10,000-row list, scroll from top to bottom in 100 steps — assert the maximum DOM node count never exceeds (containerHeight / itemHeight) + 2 * overscan + 5
  • Performance test: 100 scroll events processed in under 16ms total
  • Unit tests: useVirtualList returns correct startIndex and endIndex for various scroll positions

Acceptance Criteria

  • Maximum DOM node count bounded regardless of total holder count
  • 60fps scrolling maintained for 10,000+ row lists (no dropped frames in performance test)
  • Next page fetched automatically when within 20 rows of the end
  • Skeleton rows shown in overscan zone during page load
  • Rank and share recomputation under 5ms for 10,000 rows
  • Scroll position restored correctly on back navigation

ETA: 24 hours


Coordinate on Telegram

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaign

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions