Skip to content

Search products while typing, without waiting for Enter #162

Description

@OffCrazyFreak

What

Make the products search bar fetch while you type, instead of waiting for Enter or the search button.

Every client-filtered surface in the app is already live: updates, map, suggestions, digital-cards, shopping-lists, watchlist all pass autoSearch to the shared search bar. The four surfaces backed by the network product search are the only ones still submit-on-Enter:

  • frontend/src/components/custom/header/components/header-search.tsx
  • frontend/src/components/custom/sidebar/app-sidebar.tsx
  • frontend/src/app/(root)/components/sections/hero-actions.tsx (landing hero)
  • frontend/src/app/products/components/products-client.tsx and frontend/src/components/custom/products-sheet/products-sheet.tsx

Why it is not just flipping autoSearch

The mechanism exists at frontend/src/components/custom/search/search-bar.tsx:84, but it fires on every keystroke with no debounce and calls router.replace. That is harmless for the six pages that filter a local array. For products it would mean, per character typed:

  1. A URL write, so the products page re-reads q and refetches.
  2. One upstream call to api.cijene.dev, and often two: frontend/src/lib/cijene-api/utils/product-search-fallback.ts runs an exact pass, then a second fuzzy: true pass when the exact pass returns fewer than 10 hits and q.length >= 3. Partial words are exactly the case that trips the fallback, so mid-typing queries are the expensive ones.
  3. A fan-out of per-EAN price requests from use-filtered-product-prices.ts.

And useGetProductByName (frontend/src/lib/cijene-api/query-hooks.ts:79) has no placeholderData, so every new query key drops back to isLoading and blanks the list. Typing would flash the results empty on each character.

Work needed

  • A debounce hook. None exists in the repo; the only setTimeout is the form-draft flush in hooks/use-form-draft.ts:108. Roughly 300 to 400 ms feels right, to be tuned.
  • placeholderData: keepPreviousData on useGetProductByName, so the previous results stay on screen while the next query resolves. Pair it with an isFetching affordance rather than a full skeleton.
  • A minimum query length guard, at least 3 characters, matching MIN_FUZZY_QUERY_LENGTH. Also trim before the enabled check: enabled: Boolean(params.q) currently passes whitespace.
  • Decide router.replace vs push while typing. It has to be replace, otherwise every keystroke becomes a history entry and Back is unusable. useSearchNavigation already does this for syncQuery.
  • Reconcile search-action-button.tsx. Its isUnchanged disabled state is meaningless once the URL tracks typing, so the button either becomes a no-op or changes role.
  • Decide whether the header, sidebar and landing bars go live too. Those navigate to /products from another route, so live typing there means navigating away mid-word. Probably keep them submit-on-Enter and only make the products page and the products sheet live.
  • Confirm the upstream quota. There is no rate limiting in this repo on either side, and this change multiplies request volume against a shared token (CIJENE_API_TOKEN).

Difficulty

Medium. The frontend plumbing is a handful of small changes and is genuinely easy. The risk is not the code, it is the request volume against an external API we do not own and cannot rate limit from the server side, plus the fuzzy fallback doubling calls precisely on partial queries.

Worth considering as a cheaper middle ground: keep the fetch on submit, but add a local suggestions dropdown fed by already-cached results, so typing feels live without new network calls. Related open work is #45 (batch price history) which would reduce the same fan-out.

Notes

  • docs/SEARCH.md §9 records that the Spring backend has no search at all, so there is no autocomplete or typeahead endpoint to lean on, ours or upstream. Upstream /v1/products takes q, fuzzy and limit only, capped at 100 rows, with no paging.
  • If this ships, docs/SEARCH.md needs updating.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions