Skip to content

fix(TC-2443): prevent redundant autocomplete API calls on Search page navigation#1116

Merged
carlosthe19916 merged 1 commit into
guacsec:mainfrom
carlosthe19916:hotfix/search-dupplicate
Jul 1, 2026
Merged

fix(TC-2443): prevent redundant autocomplete API calls on Search page navigation#1116
carlosthe19916 merged 1 commit into
guacsec:mainfrom
carlosthe19916:hotfix/search-dupplicate

Conversation

@carlosthe19916

@carlosthe19916 carlosthe19916 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes TC-2443 — Navigating to the Search page after performing a search triggered redundant autocomplete API calls with an empty query, causing a brief confusing spinner popup and doubled API load.

Issues addressed

  1. Redundant autocomplete calls on navigation — After searching (e.g. "quarkus") and navigating back to the Search page via the left nav, useAllEntities fired 4 extra API requests (limit=5&q=) because isSearchValueDirty was never reset when the search value was synced from external state (URL params).

  2. Stale value flash on clear/re-submit — After searching "something", clearing the input, and pressing Enter, the previous search term briefly flashed in the input. This happened because onChangeSearch("") updates URL params asynchronously, so the context still held the old filter value for one render cycle.

  3. Unstable useEffect dependency — The sync useEffect depended on sbomTableControls.filterState (an object recreated every render due to the non-memoized context chain). Replaced with appliedSearchValue, a stable string primitive that only changes when the actual URL filter param changes.

Changes

SearchMenu.tsx:

  • Extract appliedSearchValue as a stable string from filterState — avoids the unstable object reference
  • Guard the sync useEffect with isSearchValueDirty to prevent overwriting user input during typing
  • Add submittedSearchValueRef to bridge the async gap between calling onChangeSearch() and the context reflecting the new value — prevents the stale flash
  • Combine !isSearchValueDirty || debouncedSearchValue.trim() === "" into disableAutocomplete — prevents empty-query autocomplete during both navigation AND the debounce window
  • Reset isSearchValueDirty in onSubmitInput and onClearSearchValue
  • Propagate clear to table controls via onChangeSearch("") in onClearSearchValue

SearchMenu.test.tsx (new):

  • 6 unit tests validating the autocomplete fetch gating behavior

Before / After

Before fix

Notice the autocomplete generates 8 calls.

Screencast.From.2026-06-30.09-06-26.mp4

After fix

Notice the autocomplete generates 4 calls

Screencast.From.2026-06-30.09-05-29.mp4

Test plan

  • Unit tests pass (SearchMenu.test.tsx — 6 tests)
  • Manual: Search → navigate away → navigate back: no autocomplete popup/requests
  • Manual: Type → Enter → clear → Enter: no flash of previous search term
  • Manual: Autocomplete still works when typing a new search term

🤖 Generated with Claude Code

Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
@sourcery-ai

sourcery-ai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors SearchMenu’s search state synchronization and autocomplete gating to avoid redundant empty-query API calls and stale search flashes, and adds focused tests around the new behavior.

Sequence diagram for SearchMenu autocomplete gating and state sync

sequenceDiagram
  actor User
  participant SearchMenu
  participant SbomSearchContext
  participant useAllEntities

  rect rgb(230,230,230)
    User->>SearchMenu: onSubmitInput
    SearchMenu->>SearchMenu: setIsSearchValueDirty(false)
    SearchMenu->>SearchMenu: setIsAutocompleteOpen(false)
    SearchMenu->>SearchMenu: submittedSearchValueRef.current = searchValue
    SearchMenu->>SbomSearchContext: onChangeSearch(searchValue)
  end

  rect rgb(230,230,230)
    SbomSearchContext-->>SearchMenu: appliedSearchValue
    SearchMenu->>SearchMenu: useEffect([appliedSearchValue,isSearchValueDirty])
    alt isSearchValueDirty is false and submittedSearchValueRef matches
      SearchMenu->>SearchMenu: setSearchValue(appliedSearchValue)
      SearchMenu->>SearchMenu: submittedSearchValueRef.current = null
    else isSearchValueDirty is true
      SearchMenu->>SearchMenu: [skip sync to avoid overwriting typing]
    end
  end

  rect rgb(230,230,230)
    User->>SearchMenu: type into input
    SearchMenu->>SearchMenu: setSearchValue(newValue)
    SearchMenu->>SearchMenu: setIsSearchValueDirty(true)
    SearchMenu->>SearchMenu: setDebouncedSearchValue(searchValue)
    SearchMenu->>SearchMenu: disableAutocomplete = !isSearchValueDirty || debouncedSearchValue.trim()==""

    alt debouncedSearchValue non-empty and dirty
      SearchMenu->>useAllEntities: useAllEntities(debouncedSearchValue, false)
    else navigation/submit/clear or empty query
      SearchMenu->>useAllEntities: useAllEntities(debouncedSearchValue, true)
    end
  end

  rect rgb(230,230,230)
    User->>SearchMenu: onClearSearchValue
    SearchMenu->>SearchMenu: setSearchValue("")
    SearchMenu->>SearchMenu: setIsSearchValueDirty(false)
    SearchMenu->>SearchMenu: setIsAutocompleteOpen(false)
    SearchMenu->>SearchMenu: submittedSearchValueRef.current = ""
    SearchMenu->>SbomSearchContext: onChangeSearch("")
  end
Loading

File-Level Changes

Change Details Files
Stabilized search state synchronization with URL-driven filter state and prevented overwriting user-typed input.
  • Extracts a stable appliedSearchValue string from filterState to avoid using a new object reference each render.
  • Updates the sync useEffect to depend on appliedSearchValue and early-return when isSearchValueDirty is true, preventing clobbering in-progress typing.
  • Uses submittedSearchValueRef to coordinate between async onChangeSearch updates and the context’s appliedSearchValue, clearing the ref once the URL-synced value matches.
client/src/app/pages/search/components/SearchMenu.tsx
Tightened autocomplete fetch gating to prevent redundant or empty-query requests and to reset gating correctly on submit/clear.
  • Introduces disableAutocomplete combining !isSearchValueDirty with debouncedSearchValue.trim()==="" and passes it as the flag to useAllEntities.
  • Ensures whitespace-only input keeps autocomplete disabled even while dirty, avoiding empty-query calls.
  • Resets isSearchValueDirty and closes the autocomplete dropdown on submit and clear, and propagates clear actions via onChangeSearch("").
client/src/app/pages/search/components/SearchMenu.tsx
Added unit tests to validate autocomplete gating and stale-input behavior for SearchMenu.
  • Sets up Vitest-based tests that mock useFetch* hooks and derive disable flags from their call arguments.
  • Covers initial mount, typing non-empty and whitespace-only queries, submit, clear, and stale filter-text scenarios.
  • Asserts that autocomplete flags are correctly enabled/disabled and that the input does not briefly show stale values when clearing and resubmitting.
client/src/app/pages/search/components/SearchMenu.test.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 53.05%. Comparing base (319e799) to head (0c1a7a5).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1116      +/-   ##
==========================================
+ Coverage   52.70%   53.05%   +0.34%     
==========================================
  Files         270      270              
  Lines        5868     5884      +16     
  Branches     1839     1844       +5     
==========================================
+ Hits         3093     3122      +29     
+ Misses       2466     2459       -7     
+ Partials      309      303       -6     
Flag Coverage Δ
e2e 70.69% <76.47%> (-0.23%) ⬇️
unit 6.97% <88.88%> (+3.72%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

@carlosthe19916 carlosthe19916 added this pull request to the merge queue Jul 1, 2026
Merged via the queue into guacsec:main with commit 0ce3117 Jul 1, 2026
19 checks passed
@github-project-automation github-project-automation Bot moved this to Done in Trustify Jul 1, 2026
@carlosthe19916 carlosthe19916 added the backport release/0.5.z This PR should be backported to release/0.5.z branch. label Jul 2, 2026
@carlosthe19916

Copy link
Copy Markdown
Collaborator Author

/backport

@trustify-ci-bot

Copy link
Copy Markdown
Contributor

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

Labels

backport release/0.5.z This PR should be backported to release/0.5.z branch.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants