Skip to content

perf(dropdown): Virtualize the All Projects settings submenu - #129659

Draft
sentry-junior[bot] wants to merge 2 commits into
masterfrom
perf/virtualize-project-settings-submenu
Draft

sentry-junior[bot] wants to merge 2 commits into
masterfrom
perf/virtualize-project-settings-submenu

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Hovering View All Projects in the settings v3 organization menu was slow in organizations with many projects. The submenu rendered a full DropdownMenuItem for every project, and each row includes react-aria hooks, a Link, a ProjectAvatar, and three tooltips. Each hover changed the submenu's focusedKey, which gave every row a new TreeState, so all rows re-rendered on each hover.

This PR adds an opt-in virtualizeThreshold to DropdownMenu submenu options. Once a submenu has more items than the threshold, only the visible rows (plus overscan) render, using the existing useVirtualRows / @tanstack/react-virtual setup that CompactSelect already uses. The project settings submenu turns it on above 50 projects. Smaller orgs, and every other DropdownMenu, keep the current non-virtualized path.

Jest benchmark (React dev build, rows mocked at 32px in a 240px menu), time to open the submenu and per-row hover time, before → after:

  • 100 projects: ~480ms / ~150ms → ~100ms / ~30ms
  • 284 projects: ~1.1s / ~370ms → ~90ms / ~27ms
  • 1000 projects: ~3.5s / ~1.2s → ~95ms / ~27ms

Approach / review focus (static/app/components/core/dropdownMenu/list.tsx)

  • The collection is flattened into heading, item, and separator rows so each one can be measured. Visible rows are then grouped back into their sections, which keeps role="group" semantics. A section whose heading is scrolled out of view stays labelled with aria-label.
  • Item rows are wrapped in a presentational li that carries the 2xs gap as padding. That lets the virtualizer measure the gap instead of it coming from flex gap, so the visual spacing doesn't change.
  • Keyboard: React Aria can't scroll to rows that aren't mounted. An effect scrolls to the focused key when focus moves by keyboard (only when the menu is focused, matching React Aria's own behavior). The existing "wrap to top" and "ArrowUp to search" checks compare data-key against the first and last enabled keys instead of the first and last rendered elements. ArrowDown from search focuses the first item through the selection manager.
  • Hidden rows can't size the overlay, so a virtualized submenu uses maxWidth as its width.

Known limits

  • Virtualized submenus don't support nested submenus. This is documented on the option; the projects submenu has none.
  • This doesn't fix the shared DropdownMenu re-rendering every row on hover in non-virtualized menus. It only stops mattering here because few rows are mounted. A broader fix (memoizing rows or narrowing what each row receives from TreeState) could be a follow-up.

Not yet verified in a real browser. Coverage so far is jest (2 new virtualized submenu tests, all existing DropdownMenu and OrganizationDropdown specs pass) and pnpm run typecheck. Before marking this ready, please sanity-check scrolling, keyboard navigation, and search in pnpm dev-ui against a large org.

via @priscilawebdev.

--

View Session [Sentry]

The All Projects submenu in the settings navigation rendered a full menu row
for every project, and each hover re-rendered every row. Large organizations
made opening the submenu and moving through it slow.

DropdownMenu submenus now accept virtualizeThreshold. Above it, only the
visible rows render, sections keep their group semantics, and keyboard focus
scrolls rows that are not rendered into view. The project settings submenu
turns this on above 50 projects.

Co-Authored-By: Priscila Andre de Oliveira <29228205+priscilawebdev@users.noreply.github.com>
@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Story previews

Preview the stories changed in this PR on the Vercel deployment:

Preview deployment: https://sentry-8optxuqhk.sentry.dev

Move the virtualized row rendering, the virtualizer setup, and its keyboard helpers out of
list.tsx into virtualCollection.tsx. Both render paths now share one separator component, so
they do not drift apart.

Menus with nested sub-menus no longer virtualize. A sub-menu trigger must stay mounted to
keep its sub-menu open, so these menus render every row and warn in development instead.

This branch was successfully deployed

1 active deployment
Preview — 77772db4 Deployed Nov 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant