58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
'use client';
|
|||
|
|
|
||
|
|
import { useEffect, useRef, useState } from 'react';
|
||
|
|
import { fetchSuggestions, SUGGEST_MIN_QUERY, type Suggestion } from '@/lib/suggest';
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Long enough that a fast typist does not fire a request per character, short
|
||
|
|
* enough that the list feels attached to the keyboard.
|
||
|
|
*/
|
||
|
|
const DEBOUNCE_MS = 200;
|
||
|
|
|
||
|
|
export function useSchoolSuggest(query: string, enabled: boolean) {
|
||
|
|
const [suggestions, setSuggestions] = useState<Suggestion[]>([]);
|
||
|
|
const [open, setOpen] = useState(false);
|
||
|
|
const [activeIndex, setActiveIndex] = useState(-1);
|
||
|
|
// Set when the user dismisses the list, so a re-render does not reopen it.
|
||
|
|
const dismissed = useRef('');
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const q = query.trim();
|
||
|
|
if (!enabled || q.length < SUGGEST_MIN_QUERY || dismissed.current === q) {
|
||
|
|
setSuggestions([]);
|
||
|
|
setOpen(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Abort the superseded request on every keystroke. This is correctness,
|
||
|
|
* not economy: without it a slow response for "st" can land after the fast
|
||
|
|
* one for "st marys" and replace a correct list with a stale one.
|
||
|
|
*/
|
||
|
|
const controller = new AbortController();
|
||
|
|
const timer = setTimeout(async () => {
|
||
|
|
const rows = await fetchSuggestions(q, controller.signal);
|
||
|
|
if (controller.signal.aborted) return;
|
||
|
|
setSuggestions(rows);
|
||
|
|
setActiveIndex(-1);
|
||
|
|
setOpen(rows.length > 0);
|
||
|
|
}, DEBOUNCE_MS);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
clearTimeout(timer);
|
||
|
|
controller.abort();
|
||
|
|
};
|
||
|
|
}, [query, enabled]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
suggestions,
|
||
|
|
open,
|
||
|
|
activeIndex,
|
||
|
|
setActiveIndex,
|
||
|
|
close: () => {
|
||
|
|
dismissed.current = query.trim();
|
||
|
|
setOpen(false);
|
||
|
|
setActiveIndex(-1);
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|