updated search suggestions functionality
This commit is contained in:
74
src/hooks/useSuggestions.js
Normal file
74
src/hooks/useSuggestions.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
|
||||
/**
|
||||
* Debounce a fast-changing value (here, whatever is typed in the search box).
|
||||
* 180ms is roughly two requests per word at 60wpm - responsive without firing
|
||||
* on every keystroke.
|
||||
*/
|
||||
export function useDebouncedValue(value, delay = 180) {
|
||||
const [debounced, setDebounced] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebounced(value), delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, [value, delay]);
|
||||
|
||||
return debounced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Brand/category suggestions for a partial query.
|
||||
*
|
||||
* Responses can arrive out of order, so a monotonic request id decides which
|
||||
* one is allowed to land - the same guard HomePage uses for search results.
|
||||
* Errors resolve to an empty list rather than propagating: a failing suggest
|
||||
* endpoint must never stop someone typing or submitting a search.
|
||||
*/
|
||||
export function useSuggestions(query, { enabled = true, minLength = 2, limit = 8 } = {}) {
|
||||
const debounced = useDebouncedValue(query);
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const requestIdRef = useRef(0);
|
||||
const cacheRef = useRef(new Map());
|
||||
|
||||
useEffect(() => {
|
||||
const trimmed = (debounced || '').trim();
|
||||
|
||||
if (!enabled || trimmed.length < minLength) {
|
||||
requestIdRef.current += 1; // invalidate anything still in flight
|
||||
setSuggestions([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheKey = `${limit}|${trimmed.toLowerCase()}`;
|
||||
const cached = cacheRef.current.get(cacheKey);
|
||||
if (cached) {
|
||||
// Backspacing back through text already typed shouldn't refetch.
|
||||
requestIdRef.current += 1;
|
||||
setSuggestions(cached);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = ++requestIdRef.current;
|
||||
setLoading(true);
|
||||
|
||||
api.suggest(trimmed, { limit })
|
||||
.then((res) => {
|
||||
const results = res.suggestions || [];
|
||||
cacheRef.current.set(cacheKey, results);
|
||||
if (id === requestIdRef.current) setSuggestions(results);
|
||||
})
|
||||
.catch(() => {
|
||||
if (id === requestIdRef.current) setSuggestions([]);
|
||||
})
|
||||
.finally(() => {
|
||||
if (id === requestIdRef.current) setLoading(false);
|
||||
});
|
||||
}, [debounced, enabled, minLength, limit]);
|
||||
|
||||
return { suggestions, loading };
|
||||
}
|
||||
Reference in New Issue
Block a user