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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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