Build a reusable Autocomplete component that debounces input, caches results, cancels stale requests and follows the ARIA combobox pattern.
Requirements:
- Suggestions appear as the user types.
- Keyboard navigation works.
- Matching text is highlighted.
- Recent searches show when the input is empty.
- It works on mobile and is accessible.
Non-functional requirements:
- Suggestions appear in about 100 to 200 ms.
- Server load stays low.
- It is reusable across products.
Architecture: an Autocomplete component with an input, a listbox popup and option renderers. A hook manages the query, the results, the active index and the open state. A data source function is injected (passed in), so the same component works for users, products or places.
API: GET /suggest?q=lap&limit=8. It returns items with an id, label, type and highlight ranges.
Optimisations:
- Debounce the input, about 150 to 300 ms. Debounce means wait until the user stops typing for that long before sending.
- Set a minimum query length.
- Cancel stale requests with
AbortController, or ignore responses that arrive out of order. - Keep an in-memory LRU cache keyed by query, so backspacing is instant. LRU (least recently used) means the entries unused for longest are dropped first.
- Filter prefix results locally from an earlier, broader query. For example, results for "lapt" can be filtered from the results for "lap".
Accessibility: use the combobox pattern with role="combobox", aria-expanded, aria-controls and aria-activedescendant. Add a live region that announces the number of results. A live region is an area that screen readers read aloud when it changes.
Trade-offs: a longer debounce means fewer requests, but the box feels slower.
- Debounce input, set a minimum length, and cancel or ignore stale responses.
- Cache results on the client per query.
- Use the ARIA combobox pattern with
aria-activedescendant. - Inject the data source so the component can be reused.
- Handle loading, empty and error states.