- 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
85 lines
1.9 KiB
TypeScript
85 lines
1.9 KiB
TypeScript
'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
|
|
}
|
|
}
|