Skip to content

Replace SelectField with a virtualized Combobox for large-schema selects - #2113

Open
mjuarros wants to merge 8 commits into
aws:mainfrom
mjuarros:main
Open

Replace SelectField with a virtualized Combobox for large-schema selects#2113
mjuarros wants to merge 8 commits into
aws:mainfrom
mjuarros:main

Conversation

@mjuarros

@mjuarros mjuarros commented Aug 14, 2026

Copy link
Copy Markdown

Description

SelectField renders every option as a DOM node unconditionally. With a schema of 10,000+ vertex/edge types, this froze the UI for several seconds every time the Data Explorer node-type picker, the Search Sidebar's node-type filter, or its attribute filter was opened.

This replaces those three pickers with a new Combobox component built on Base UI's accessible combobox primitives plus @tanstack/react-virtual for windowed rendering, so only the visible options (~20) are ever mounted regardless of how many types the schema has. Type-to-filter narrows the list as you type. Every other SelectField usage in the app is untouched — they're all either fixed-size enums or scoped to a single vertex/edge type's own attributes, so they were never at risk of this.

How to read

  1. Combobox.tsx — the new component; start here.
  2. DataExplorer.tsx, FilterSearchTabContent.tsx, useKeywordSearch.ts — the three sites swapped from SelectField to Combobox.
  3. displayTypeConfigs.ts — a small pre-existing memoization bug in useSearchableAttributes fixed along the way (found while restoring a referential-stability guarantee post-migration); tangential to the main change.
  4. Combobox.test.tsx, useKeywordSearch.test.ts — tests, including a 10,000-item scale guardrail.

Validation

Manually validated against a synthetic 10,000-vertex-type / 10,000-edge-type dataset: the Data Explorer and Search Sidebar pickers previously froze the page for several seconds on open; they now open instantly regardless of schema size. Confirmed with an isolated real-Chromium test harness that open latency stays flat (~30ms) across 10, 500, and 10,000 items — a virtualizer-wiring bug that briefly regressed this was caught the same way and fixed before landing.

pnpm checks and pnpm test pass (219 test files, 2696 tests).

Related Issues

Check List

  • I confirm that my contribution is made under the terms of the Apache 2.0 license.
  • I have verified pnpm checks passes with no errors.
  • I have verified pnpm test passes with no failures.
  • I have covered new added functionality with unit tests if necessary.
  • I have updated documentation if necessary.

mjuarrosImproving and others added 6 commits August 14, 2026 10:56
Backs large option lists (5,000-10,000+ items) with @tanstack/react-virtual
so rendering stays smooth instead of freezing the UI, matching the look
and keyboard/animation behavior of the existing Select component.

Also supports an inner label above the value, matching SelectField's
labelPlacement="inner" style, so sites migrating off SelectField don't
lose that caption.
Swaps the node-type dropdowns in FilterSearchTabContent and DataExplorer
over to the virtualized Combobox to fix UI freezing on schemas with large
numbers of vertex/edge types. DataExplorer uses the inner label to keep
the same caption the SelectField's labelPlacement="inner" showed.
The previous commit shipped a hand-rolled Combobox instead of the
@base-ui/react primitives the proposal called for. This finishes that
migration, then fixes what verifying it surfaced: a popup width
mismatch, an arrow button that didn't close on a second click, an
entrance animation twice as long as every other dropdown, and an
empty-state element reserving phantom space above the list.

The significant fix: the virtualizer was measuring the wrong scroll
element, so it rendered nearly all 10,000 options as real DOM nodes
on every open instead of ~20 - confirmed with an isolated Chromium
harness (~1.75s per open before, ~30ms after). Also fixes a
memoization bug in useSearchableAttributes surfaced while restoring
its referential-stability guarantee, and updates the test suite's
offsetHeight/offsetWidth mock to actually distinguish which element
gets measured.
…bility issues

The virtualized prop was missing from BaseCombobox.Root, silently bounding
keyboard navigation to whatever was mounted in the DOM window instead of
the full option count. Open/filter state was smeared across handlers that
didn't distinguish typed input from Base UI's own programmatic syncs,
causing stale display text and a popup that never closed on focus-out.
The toggle button is no longer hidden from the accessibility tree: when
the input already has a value, VoiceOver's Read-All treats it as content
to read and skips announcing its combobox role, so the button is what
re-announces it in that case.

Also fixes popup sizing/animation, prop passthrough safety, filtering
efficiency, and several tests that didn't assert what they claimed.
useTranslations() returned a fresh function on every render, which fed
into useAttributeOptions's own useMemo dependency array and defeated it
every render regardless of whether the underlying data changed. vertexOptions
had the same problem one level up: a fresh array of fresh objects on every
render, defeating Combobox's own filteredOptions memo on every keystroke.
…ive nanoid

@base-ui/react and @tanstack/react-virtual were declared in both the
workspace root and packages/graph-explorer, though only graph-explorer
imports them. Also adds an override for nanoid (GHSA-2v37-7h3g-55p8),
pulled in transitively via vite > postcss and confined to dev, following
the existing override pattern for esbuild/dompurify/undici.
…-filter support

Adds an ADR for introducing @base-ui/react and @tanstack/react-virtual
alongside the existing Radix/shadcn stack, exceptions to the React
Compiler guidance for the memoization and useVirtualizer cases this PR
needed, the Base UI vs Radix data-attribute divergence, and the jsdom
offsetHeight gotcha the test suite works around. Also updates user-facing
docs to mention that the node-type/attribute pickers now support typing
to filter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants