feat: implement client-side state management hooks

- Created useSearch hook for search state and debouncing
- Created useFilters hook for filter state management
- Created useOptimisticUpdate hook for optimistic UI updates
- Created useScrollPosition hook for scroll preservation
- Updated SearchBar, GlobalSearch, and ListDetailView to use new hooks
- Added comprehensive documentation for all hooks

Validates: Requirements 6.4, 7.3, 7.4, 8.3
Task: 9. Implement client-side state management
This commit is contained in:
2025-12-01 20:11:41 +00:00
parent 0143097e69
commit e2e3581d06
10 changed files with 821 additions and 52 deletions

84
hooks/useSearch.ts Normal file
View File

@@ -0,0 +1,84 @@
'use client'
import { useState, useEffect, useCallback, useRef } from 'react'
interface UseSearchOptions {
debounceMs?: number
onSearch?: (query: string) => void | Promise<void>
}
interface UseSearchReturn {
query: string
setQuery: (query: string) => void
debouncedQuery: string
isDebouncing: boolean
clearSearch: () => void
}
/**
* Custom hook for managing search state with debouncing
* Validates: Requirements 6.4, 8.3
*/
export function useSearch(options: UseSearchOptions = {}): UseSearchReturn {
const { debounceMs = 300, onSearch } = options
const [query, setQuery] = useState('')
const [debouncedQuery, setDebouncedQuery] = useState('')
const [isDebouncing, setIsDebouncing] = useState(false)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
// Debounce the query
useEffect(() => {
// If query is empty, update immediately
if (!query.trim()) {
setDebouncedQuery('')
setIsDebouncing(false)
return
}
setIsDebouncing(true)
// Clear existing timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
// Set new timeout
timeoutRef.current = setTimeout(() => {
setDebouncedQuery(query)
setIsDebouncing(false)
}, debounceMs)
// Cleanup
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
}
}, [query, debounceMs])
// Call onSearch callback when debounced query changes
useEffect(() => {
if (onSearch) {
onSearch(debouncedQuery)
}
}, [debouncedQuery, onSearch])
// Clear search function
const clearSearch = useCallback(() => {
setQuery('')
setDebouncedQuery('')
setIsDebouncing(false)
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
}, [])
return {
query,
setQuery,
debouncedQuery,
isDebouncing,
clearSearch
}
}