Problem Statement
Build a product search box that shows matching products from the dummyjson API as the user types.
Functional Requirements
- Debounced search: Send the request 1 second after the user stops typing, using
https://dummyjson.com/products/search?q=<query>&limit=10. - Results list: Show up to 10 products, each with a thumbnail, title, and price.
- Status messages: Show "Searching..." while loading and an error message if the request fails.
- Clear button: When the input has text, an × button clears the query and the results and focuses the input.
- Empty query: A blank query shows no results and sends no request.
Technical Constraints
- Cancel the previous request with an
AbortControllerwhen the query changes. - Cache results per query in a
useRefobject, 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 ItemsKey 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
refsfor 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
useReffor 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();
};