The three `getLibraryBrowseItems` pickers (RerunCollectionsScreen, PlaylistsScreen, FillerPresetsScreen) populated a native <select> from a 100-row window over media-library tables that can hold tens of thousands of rows. #644 made that truncation visible; it did not make the picker usable, and paging to completeness would have been worse than the bug (~200 serial requests, each more expensive than the last). They now resolve by SEARCH through the shared `SearchPicker` over a new `searchLibraryPickerOptions` helper: zero requests on mount or on a type switch, at most ONE bounded request (25 rows) per settled query, nothing below 2 characters. Typed text is compiled via the now-shared `titleContainsQuery` (`title:*<escaped>*`) rather than forwarded raw, since the index's default field does not match bare title words. The current selection renders from the owning record — `selectedName` for rerun collections and playlist items, and for filler presets (which store only an id) a single by-id detail read — so editing an existing record can never lose or fail to name its selection. Class A stays put: bounded-by-construction admin lists still page to completeness via `loadAllPages`, and the collection-family filler-preset types keep their bounded single page (their `query` is a SQL LIKE, which a compiled Lucene query would not match). No server-side cap is raised; this is a web-only change. Folded in from #578: the rule-builder facet typeahead arms on focus rather than on mount (an N-rule tree fired N unrequested lookups), both typeaheads pair their `seqRef` guard with a shared `useIsMountedRef`, and the roundtrip test's LCG divides by 2^32 so `pick()` can no longer index one past the end. Decision record `spa.list-completeness-vs-bounded-pickers` is archived as superseded by the new `spa.library-pickers-resolve-by-search`; spa-conventions §3b rewritten to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
22 lines
822 B
TypeScript
22 lines
822 B
TypeScript
import { useEffect, useRef } from 'react';
|
|
|
|
// Shared is-mounted guard for async callbacks (#578). A monotonic `seqRef` drops a STALE response
|
|
// (an older request resolving after a newer one) but says nothing about whether the component is
|
|
// still there: a fetch that resolves after unmount still matches the latest seq and still calls
|
|
// setState. Read `ref.current` in every async callback alongside the seq check.
|
|
//
|
|
// The flag is (re-)armed inside the effect rather than only at `useRef` init so a StrictMode
|
|
// double-invoke — mount, unmount, remount on the same instance — leaves it true.
|
|
export function useIsMountedRef(): { readonly current: boolean } {
|
|
const ref = useRef(true);
|
|
|
|
useEffect(() => {
|
|
ref.current = true;
|
|
return () => {
|
|
ref.current = false;
|
|
};
|
|
}, []);
|
|
|
|
return ref;
|
|
}
|