data-flow: add debounce to LibraryPage search and fix race condition

- Completed Actions 2.4.1.1, 2.4.1.2, and 2.4.1.4
- Action 2.4.1.1: Verified custom useDebounce hook exists (no external package needed)
- Action 2.4.1.2: Added useDebounce hook with 300ms delay to LibraryPage search
- Action 2.4.1.4: Fixed race condition by using debouncedSearchTerm for page reset
- Search now fires 300ms after typing stops, reducing API calls
- Page reset now waits for debounce to complete, preventing race conditions
This commit is contained in:
senke 2026-01-11 16:49:07 +01:00
parent a37c52890f
commit ff589b73c5
2 changed files with 23 additions and 18 deletions

View file

@ -632,32 +632,32 @@ Critical path dependencies:
### Sub-Epic 2.4: Request Debouncing 🟢
#### Task 2.4.1: Add Debounce to Search Inputs
- [ ] **Action 2.4.1.1**: Install `use-debounce` or implement custom hook
- [x] **Action 2.4.1.1**: Install `use-debounce` or implement custom hook
- **Scope**: `apps/web/package.json` - Add dependency (if using library)
- **Dependencies**: None
- **Dependencies**: None
- **Risk**: LOW
- **Validation**: Package installed
- **Rollback**: Remove from package.json
- **Validation**: ✅ Custom `useDebounce` hook already exists at `apps/web/src/hooks/useDebounce.ts` with tests. No external package needed.
- **Rollback**: N/A (custom implementation)
- [ ] **Action 2.4.1.2**: Add debounce to LibraryPage search
- [x] **Action 2.4.1.2**: Add debounce to LibraryPage search
- **Scope**: `apps/web/src/features/library/pages/LibraryPage.tsx:322-327` - Debounce `setSearchTerm`
- **Dependencies**: Action 2.4.1.1 complete
- **Dependencies**: Action 2.4.1.1 complete
- **Risk**: LOW
- **Validation**: Search fires 300ms after typing stops
- **Rollback**: Remove debounce
- **Validation**: ✅ Added `useDebounce` hook with 300ms delay. Search term is debounced before being used in queryParams and queryKey. Search fires 300ms after typing stops.
- **Rollback**: Remove debounce hook usage
- [ ] **Action 2.4.1.3**: Add debounce to all search inputs
- **Scope**: Audit all search inputs, add debounce
- **Dependencies**: Action 2.4.1.2 complete
- **Dependencies**: Action 2.4.1.2 complete
- **Risk**: LOW
- **Validation**: All searches debounced
- **Rollback**: Remove debounce from each
- [ ] **Action 2.4.1.4**: Fix race condition in LibraryPage search/page reset
- [x] **Action 2.4.1.4**: Fix race condition in LibraryPage search/page reset
- **Scope**: `apps/web/src/features/library/pages/LibraryPage.tsx:116-120` - Use debounced search term for page reset
- **Dependencies**: Action 2.4.1.2 complete
- **Dependencies**: Action 2.4.1.2 complete
- **Risk**: LOW
- **Validation**: Page resets only after debounce completes
- **Validation**: ✅ Updated useEffect to use `debouncedSearchTerm` instead of `searchTerm` for page reset. Page resets only after debounce completes, fixing race condition.
- **Rollback**: Restore original useEffect
---

View file

@ -1,4 +1,5 @@
import { useState, useMemo, useEffect } from 'react';
import { useDebounce } from '@/hooks/useDebounce';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import {
usePlaylists,
@ -70,6 +71,8 @@ export default function LibraryPagePremium() {
const [viewMode, setViewMode] = useState<ViewMode>('grid');
const [searchTerm, setSearchTerm] = useState('');
// Action 2.4.1.2: Debounce search term to reduce API calls
const debouncedSearchTerm = useDebounce(searchTerm, 300);
const [genreFilter, setGenreFilter] = useState<string>('');
const [formatFilter, setFormatFilter] = useState<string>('');
const [sortBy, setSortBy] = useState<SortField>('created_at');
@ -91,9 +94,10 @@ export default function LibraryPagePremium() {
if (formatFilter) {
queryParams.format = formatFilter;
}
if (searchTerm.trim()) {
queryParams.search = searchTerm.trim();
}
// Use debounced search term for API calls
if (debouncedSearchTerm.trim()) {
queryParams.search = debouncedSearchTerm.trim();
}
const {
data: tracksData,
@ -101,7 +105,7 @@ export default function LibraryPagePremium() {
isError: isTracksError,
error: tracksError,
} = useQuery({
queryKey: ['tracks', 'library', queryParams, searchTerm],
queryKey: ['tracks', 'library', queryParams, debouncedSearchTerm],
queryFn: () => getTracks(page, limit, queryParams),
});
@ -113,11 +117,12 @@ export default function LibraryPagePremium() {
return tracksData.tracks;
}, [tracksData?.tracks]);
// Action 2.4.1.4: Use debounced search term for page reset to fix race condition
useEffect(() => {
if (searchTerm.trim() && page !== 1) {
if (debouncedSearchTerm.trim() && page !== 1) {
setPage(1);
}
}, [searchTerm]);
}, [debouncedSearchTerm, page]);
const genres = Array.from(
new Set(