Skip to content

Search Bar with Typeahead

Search products as the user types, wait 1 second before each request, cancel stale requests, and cache the last 10 queries in a ref.

Stage 4core2 min read

Problem Statement

Build a product search box that shows matching products from the dummyjson API as the user types.

Functional Requirements

  1. Debounced search: Send the request 1 second after the user stops typing, using https://dummyjson.com/products/search?q=<query>&limit=10.
  2. Results list: Show up to 10 products, each with a thumbnail, title, and price.
  3. Status messages: Show "Searching..." while loading and an error message if the request fails.
  4. Clear button: When the input has text, an × button clears the query and the results and focuses the input.
  5. Empty query: A blank query shows no results and sends no request.

Technical Constraints

  • Cancel the previous request with an AbortController when the query changes.
  • Cache results per query in a useRef object, keep at most 10 entries, and remove the oldest entry when full.

1. Code Flow (JSX)

The JSX structure follows this hierarchy:

TypeHead Component
├── typeahead-container
│ ├── search-input-wrapper
│ │ ├── Search Icon
│ │ ├── Input Field
│ │ └── Clear Button (conditional)
│ └── results-container (conditional)
├── Loading State
├── Error State
└── Results List
└── Result Items

Key Points for Interview:

  • The component uses conditional rendering extensively.
  • Shows different UI states (loading, error, success).
  • Implements a responsive search input with icons.
  • Displays results in a scrollable list with thumbnails and details.

2. Function Explanations

Main Functions:

TypeHead Component

  • Main functional component.
  • Manages state for:
    • query (search input)
    • result (search results)
    • status (loading/success/error)
    • isInputFocused (input focus state)
  • Uses refs for cache and input element.

useEffect Hook

  • Handles data fetching.
  • Implements debouncing.
  • Manages cache.
  • Controls abort controller.
  • Dependencies: [query]

handleClearInput

  • Clears search input.
  • Resets results.
  • Focuses input field.

3. Caching Implementation

Definition:

Caching is storing previously fetched data to avoid unnecessary API calls for the same queries.

Implementation:

const MAX_CACHE_SIZE = 10;
const cache = useRef(Object.create(null));

// Cache check
if (cache.current[query]) {
  setResult(cache.current[query]);
  return;
}

// Cache management
if (cacheSize >= MAX_CACHE_SIZE) {
  const firstKey = Object.keys(cache.current)[0];
  delete cache.current[firstKey];
}

// Store in cache
cache.current[query] = data.products;

Interview Points:

  • Uses useRef for persistent storage across renders.
  • Evicts the oldest entry first (first in, first out). A hit does not refresh an entry, so this is not a true LRU cache.
  • Maintains a maximum cache size of 10 items.
  • Prevents unnecessary API calls.

4. Debouncing Implementation

Definition:

Debouncing is a programming pattern that delays the execution of a function until after a certain amount of time has passed since its last invocation.

Implementation:

const timerId = setTimeout(fetchData, 1000);

return () => {
  clearTimeout(timerId);
};

Interview Points:

  • Prevents excessive API calls while typing.
  • Waits 1000ms after user stops typing.
  • Cleans up previous timeout on new input.
  • Improves performance and reduces server load.

5. Abort Controller

Definition:

AbortController is a Web API that allows you to abort one or more ongoing web requests as needed.

Implementation:

const abortController = new AbortController();
const { signal } = abortController;

fetch(url, { signal });

return () => {
  abortController.abort();
};

In this topic

Done with this topic?

How well do you know it?

PreviousImage Carousel