Skip to content

fix(select): replace cached options with search results in AsyncSelect#40039

Merged
sadpandajoe merged 7 commits into
masterfrom
fix-asyncselect-search-option-merging
May 22, 2026
Merged

fix(select): replace cached options with search results in AsyncSelect#40039
sadpandajoe merged 7 commits into
masterfrom
fix-asyncselect-search-option-merging

Conversation

@sadpandajoe
Copy link
Copy Markdown
Member

SUMMARY

Fixes two intertwined bugs in AsyncSelect that cause server search results to be buried under cached page-0 options when there are >100 total records.

Bug 1 — handleFilterOptionHelper breaks filterOption={false}:
When callers pass filterOption={false} (AntD's documented convention for "server handles filtering, show all provided options"), the helper falls through to return false, hiding every option and collapsing the dropdown to "No data".

Bug 2 — mergeData merges search results with cached page-0 options:
When a search fetch resolves, results are merged with all previously-cached options instead of replacing them. With >100 records, the actual match is appended at position 100+, buried under unrelated cached entries.

BEFORE/AFTER SCREENSHOTS OR COVERAGE

Before: Typing a search term that matches 1 record shows 101 options (100 cached page-0 + 1 match at the bottom).

After: Typing a search term shows only the server's response. Clearing the search restores the original page-0 options.

TESTING INSTRUCTIONS

  1. Set up an environment with >100 records for any AsyncSelect consumer (e.g., users, datasets, dashboards).
  2. Open a dropdown backed by AsyncSelect.
  3. Type a specific search term that matches a record beyond page 0.
  4. Verify the dropdown shows only matching results, not 100+ cached entries.
  5. Clear the search term and verify the original options are restored.

ADDITIONAL INFORMATION

Changes:

  • utils.tsx: Added explicit filterOption === false check that returns true (show all options) instead of falling through to return false.

  • AsyncSelect.tsx:

    • Added initialOptionsRef to cache base (no-search) options and wasSearchingRef to track search state.
    • Modified fetchPage to replace selectOptions on search page-0 instead of merging.
    • Modified inputValue effect to restore base options when search clears, with wasSearchingRef guard to avoid interfering with normal dropdown open behavior.
  • AsyncSelect.test.tsx: Added 3 tests covering search-replace, filterOption={false}, and search-clear restoration.

  • Has associated issue

  • Changes UI

  • Includes DB Migration

  • Changes existing API(s) or adds new API(s)

@dosubot dosubot Bot added the change:frontend Requires changing the frontend label May 11, 2026
@sadpandajoe sadpandajoe marked this pull request as draft May 11, 2026 23:22
@netlify
Copy link
Copy Markdown

netlify Bot commented May 11, 2026

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit c81ba84
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a0264acbc7b7d000876852f
😎 Deploy Preview https://deploy-preview-40039--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.tsx Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

❌ Patch coverage is 93.18182% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.15%. Comparing base (f187a8e) to head (ef0f15d).
⚠️ Report is 9 commits behind head on master.

Files with missing lines Patch % Lines
...rset-ui-core/src/components/Select/AsyncSelect.tsx 92.85% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40039      +/-   ##
==========================================
+ Coverage   64.14%   64.15%   +0.01%     
==========================================
  Files        2592     2592              
  Lines      138841   138875      +34     
  Branches    32201    32207       +6     
==========================================
+ Hits        89064    89101      +37     
+ Misses      48245    48242       -3     
  Partials     1532     1532              
Flag Coverage Δ
javascript 67.17% <93.18%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 14, 2026

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit fe12683
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a0f32b6923fcb0008c04ae8
😎 Deploy Preview https://deploy-preview-40039--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@sadpandajoe sadpandajoe force-pushed the fix-asyncselect-search-option-merging branch from 0b935a4 to 76cc1d9 Compare May 19, 2026 23:47
@sadpandajoe sadpandajoe requested a review from Copilot May 20, 2026 21:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes AsyncSelect behavior when server-side searching is enabled and option sets are large, ensuring search results are displayed correctly and filterOption={false} works as intended.

Changes:

  • Fix handleFilterOptionHelper to treat filterOption === false as “show all provided options”.
  • Update AsyncSelect fetching logic to replace cached options with search results (page 0), restore base options on search clear, and drop stale search responses; add an in-flight counter to keep loading state consistent across races.
  • Add Jest tests covering search replacement, filterOption={false}, restore-on-clear, and several race/pagination regressions.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
superset-frontend/packages/superset-ui-core/src/components/Select/utils.tsx Fixes filterOption={false} so options aren’t incorrectly hidden.
superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.tsx Reworks async option handling to replace options on search, restore base options on clear, and guard against stale responses/loading races.
superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.test.tsx Adds regression tests for search replace/restore, filterOption={false}, pagination during search, and race conditions.

@sadpandajoe sadpandajoe marked this pull request as ready for review May 21, 2026 02:35
@sadpandajoe sadpandajoe requested a review from kgabryje May 21, 2026 02:37
@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented May 21, 2026

Code Review Agent Run #4b7030

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.test.tsx - 1
    • Missing return type annotation · Line 1166-1166
      Add explicit return type annotation to `isSpinnerVisible`. Per BITO.md rule [7819], all functions including test helpers should include explicit return type hints for consistency and better static type checking.
      Code suggestion
      --- superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.test.tsx (line 1166) ---
       1166:-  const isSpinnerVisible = () =>
       1166:+  const isSpinnerVisible = (): boolean =>
       1167:    Boolean(document.querySelector('.ant-select-arrow .ant-spin'));
Review Details
  • Files reviewed - 3 · Commit Range: b986e41..bd11723
    • superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.test.tsx
    • superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.tsx
    • superset-frontend/packages/superset-ui-core/src/components/Select/utils.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

sadpandajoe and others added 6 commits May 21, 2026 09:28
When searching with >100 cached records, server search results were merged
with page-0 options instead of replacing them, burying matches at the end.
Also fixes filterOption=false falling through to return false (hiding all
options) instead of returning true (show all, server-side filtering).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ons-prop change

Preserve optimistic isNewOption entries inserted by handleOnSearch when
the search fetch resolves, so allowNewOptions users can still pick the
value they typed when the server returns no match (regression seen via
SaveModal "Add to dashboard"). Also reset initialOptionsRef and
wasSearchingRef when the options loader changes, so loader swaps don't
briefly restore stale options.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ests

Replace wasSearchingRef with usePrevious(inputValue), restructure the
fetchPage().then() branching so allValuesLoaded lives in the non-search
branch (removes the dead resultData variable), and harden the new tests
with disjoint datasets and negative assertions so they would fail against
the original merge-on-search bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Search responses no longer write to fetchedQueries — caching only the
totalCount made re-typing a previously-resolved search term short-
circuit the fetch and leave selectOptions stale (e.g. after restore-on-
clear had reset to the base list). Search refetches are cheap; only the
base accumulator benefits from the totalCount cache.

Late responses whose search arg no longer matches inputValueRef are
dropped, so a slow base or search fetch cannot pollute the dropdown
after the user has moved on. Base pages accumulate in initialOptionsRef
independently of selectOptions so restore-on-clear has a complete
snapshot even when base pages land during an active search.

Also cancel any pending debounced search fetch on clear so it cannot
fire after the base list has been restored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The stale-response early-return at line 354 drops the data but the shared
`.finally` block previously still cleared `isLoading` to false. When a user
typed 'alpha' (in-flight) then 'beta' (also in-flight), alpha's dropped
response flipped the spinner off while beta was still pending. The
undebounced `handlePagination` scroll handler reads `!isLoading` and could
then fire `fetchPage` against stale `totalCount`.

Gate `setIsLoading(false)` on an in-flight fetch counter so the spinner
only clears when every dispatched request has settled. Add regression
test covering the held-alpha + in-flight-beta race, plus coverage for
page>1 results during an active search.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
clearCache() previously only cleared fetchedQueries, leaving
initialOptionsRef populated and allValuesLoaded sticky. After
calling clearCache(), a subsequent search + clear would restore
stale options from initialOptionsRef and the next no-search fetch
could short-circuit on allValuesLoaded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sadpandajoe sadpandajoe force-pushed the fix-asyncselect-search-option-merging branch from bd11723 to fe12683 Compare May 21, 2026 16:28
@rusackas
Copy link
Copy Markdown
Member

Concern: sortComparatorForNoSearch in the accumulation. The accumulated base options are sorted on every page arrival. For large datasets this is O(n log n) on each page, where n grows. This was not in the original code and could become noticeably slow for very large cached option sets. Consider accumulating unsorted and sorting only once when restoring.

Concern: sort(sortComparatorForNoSearch) on search results. The server already returned results ranked by relevance. Sorting client-side with sortComparatorForNoSearch could reorder them and push the most relevant match down. Is this intentional? The existing mergeData also sorts, but for search results, server ranking is typically preferred. This deserves a comment or consideration.

Concern: page > 0 during search (the else branch, line 401+). The "append normally" path for search pagination pages isn't explicitly shown in the diff but falls through to the original mergeData path. Verify that paginating through search results (scrolling the dropdown) still works correctly.

The initialOptionsRef accumulator was sorted on every base-page fetch,
even though it's only consumed at restore-on-clear, which sorts a copy
at consumption time. Combined with the mergeData sort on the live
selectOptions path, this doubled per-page sort work and grew as more
pages were cached. Dedupe-and-append only; defer the sort to consumption.

Also adds an explicit return type to the isSpinnerVisible test helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@rebenitez1802 rebenitez1802 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I agree with rusackas concern related to the client side sorting over the server ranked results. I'd suggest adding a comment if intended.

@sadpandajoe
Copy link
Copy Markdown
Member Author

@rusackas @rebenitez1802

Concern: sort(sortComparatorForNoSearch) on search results. The server already returned results ranked by relevance. Sorting client-side with sortComparatorForNoSearch could reorder them and push the most relevant match down. Is this intentional? The existing mergeData also sorts, but for search results, server ranking is typically preferred. This deserves a comment or consideration.

Concern: page > 0 during search (the else branch, line 401+). The "append normally" path for search pagination pages isn't explicitly shown in the diff but falls through to the original mergeData path. Verify that paginating through search results (scrolling the dropdown) still works correctly.

Both of these have the same current behavior as master. Unsure if we want in-line comments, but the functionality hasn't changed.

@sadpandajoe sadpandajoe merged commit 3b4892c into master May 22, 2026
72 checks passed
@sadpandajoe sadpandajoe deleted the fix-asyncselect-search-option-merging branch May 22, 2026 18:25
@bito-code-review
Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

kasiazjc pushed a commit that referenced this pull request May 26, 2026
#40039)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants