Review: Debounced Product Search Written by an AI Assistant

Your team asked an AI coding assistant to build the search-as-you-type box for a marketplace's header. It opened this pull request with a custom hook and a results component. You are the reviewer, and nothing merges without your approval.

The assistant's description:

Adds useProductSearch, a debounced search hook (300 ms), and SearchResults, which highlights the matching text in each product name and supports keyboard navigation (arrow keys and Enter). Only the latest results are ever shown.

Where it runs: React 18 in the marketplace's main web app, on every page, for signed-in shoppers. Product names are written by third-party sellers. The search API answers in anything from 50 ms to 2 s under load and returns {"error": "..."} with status 429 or 500 when it is overloaded.

Your task

  • Flag every problem you would block the merge for. Click a line number to comment, and Shift-click to cover a range.
  • For each one, choose the issue type and severity, and explain what fails and how to fix it.
  • You are scored on the problems you catch, the right issue type and a clear explanation. Missing a critical problem fails the round.

Aim for 15 minutes, as in a real review round.

Product names come from sellers. Follow p.name until it reaches the page.

Type c++ or ( into the box. What does new RegExp do with it?

Type four letters quickly. How many timers are created, and how many are cancelled?

The request for ca takes 2 s and the one for cat takes 100 ms. Which results stay on screen?

The key handler is registered once, on the first render. What values of selected and results does it see for ever?

A strong reviewer blocks this on the XSS first, since it lets any seller run script in every shopper's session, and then on the claim that only the latest results are shown, which is not true.

  1. Stored XSS through product names (SearchResults.tsx line 44, critical). highlight returns the seller-written name as raw HTML and dangerouslySetInnerHTML renders it. A product called <img src=x onerror=...> runs script for every shopper who searches for it. Render the name as text and wrap the matched parts in <mark> elements in JSX, or escape the name before inserting markup.
  2. Search term used as a regular expression (SearchResults.tsx line 13, major). Typing c++, ( or [ makes new RegExp throw during render, which unmounts the search box (and, without an error boundary, the page). Characters such as . also match the wrong text. Escape the term, or match with indexOf on lower-cased strings.
  3. Key handler has stale state and is never removed (SearchResults.tsx lines 22–29, major). With an empty dependency array, onKeyDown closes over the first render's selected (0) and results (empty), so arrow keys and Enter never work. The effect also has no cleanup, so each mount adds another window listener that is never removed. Keep the latest values in a ref or list them as dependencies, and return () => window.removeEventListener("keydown", onKeyDown).
  4. Debounce timer is never cleared (useProductSearch.ts lines 42–43, major). The effect does not return () => clearTimeout(timer), so every keystroke still fires its own request 300 ms later. There is no debouncing at all, and a timer that fires after the box is cleared puts old results back.
  5. Out-of-order responses overwrite newer ones (useProductSearch.ts line 35, major). When a slow response for ca lands after a fast one for cat, the stale results win, and the older request's finally turns off the spinner while the newer one is still loading. Abort the previous request with an AbortController in the effect cleanup, or ignore responses whose query is no longer current.
  6. Error responses treated as results (useProductSearch.ts line 34, major). fetch does not reject on 429 or 500. The {"error": ...} body is stored as results, and results.map then throws during render. Check res.ok and set the error state instead.
  7. Query not URL-encoded (useProductSearch.ts line 33, minor). Searching for AT&T sends q=AT, and c++ arrives as c plus spaces. Use encodeURIComponent(query) or URLSearchParams.
Next Post Previous Post
No Comment
Add Comment
comment url