- 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
Custom Hooks Documentation
This directory contains custom React hooks for client-side state management in the Ultimate Lists App.
Hooks Overview
useSearch
Manages search state with debouncing functionality.
Features:
- Automatic debouncing of search queries
- Immediate clear functionality
- Tracks debouncing state
Usage:
import { useSearch } from '@/hooks'
function MyComponent() {
const { query, setQuery, debouncedQuery, isDebouncing, clearSearch } = useSearch({
debounceMs: 300,
onSearch: async (query) => {
// Perform search with debounced query
const results = await searchItems(query)
}
})
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
)
}
useFilters
Manages filter state for sorting and filtering items.
Features:
- Centralized filter state management
- Detects active filters
- Easy filter clearing
- Individual filter updates
Usage:
import { useFilters } from '@/hooks'
function MyComponent() {
const { filters, setFilters, updateFilter, clearFilters, hasActiveFilters } = useFilters({
initialFilters: { sortBy: 'createdAt' },
onFiltersChange: async (filters) => {
// Apply filters
const results = await getItemsByFilter(listId, filters)
}
})
return (
<div>
<button onClick={() => updateFilter('priority', 'high')}>
High Priority
</button>
{hasActiveFilters && (
<button onClick={clearFilters}>Clear Filters</button>
)}
</div>
)
}
useOptimisticUpdate
Provides optimistic UI updates for better perceived performance.
Features:
- Immediate UI feedback
- Automatic rollback on error
- Pending state tracking
- Error handling
Usage:
import { useOptimisticUpdate } from '@/hooks'
function MyComponent() {
const { optimisticData, isPending, error, performUpdate } = useOptimisticUpdate({
onUpdate: async (data) => {
await updateItem(data.id, data)
},
onError: (error) => {
console.error('Update failed:', error)
}
})
const handleToggle = async () => {
await performUpdate({ id: '123', completed: true })
}
return (
<button onClick={handleToggle} disabled={isPending}>
{isPending ? 'Updating...' : 'Toggle'}
</button>
)
}
useOptimisticList
Specialized hook for managing lists with optimistic updates.
Usage:
import { useOptimisticList } from '@/hooks'
function MyComponent({ initialItems }) {
const { optimisticList, isPending, addItem, updateItem, removeItem } = useOptimisticList(
initialItems,
async (updatedList) => {
// Sync with server
await syncList(updatedList)
}
)
return (
<div>
{optimisticList.map(item => (
<div key={item.id}>
{item.title}
<button onClick={() => removeItem(item.id)}>Delete</button>
</div>
))}
</div>
)
}
useScrollPosition
Preserves scroll position across navigation for better UX.
Features:
- Automatic scroll position saving
- Restoration on component mount
- Debounced save operations
- SessionStorage-based persistence
Usage:
import { useScrollPosition } from '@/hooks'
function MyComponent({ listId }) {
// Automatically saves and restores scroll position
useScrollPosition({
key: `list-${listId}`,
enabled: true,
debounceMs: 100
})
return (
<div>
{/* Your scrollable content */}
</div>
)
}
useContainerScrollPosition
Similar to useScrollPosition but for specific container elements.
Usage:
import { useContainerScrollPosition } from '@/hooks'
function MyComponent({ listId }) {
const { containerRef } = useContainerScrollPosition({
key: `container-${listId}`
})
return (
<div ref={containerRef} style={{ height: '400px', overflow: 'auto' }}>
{/* Your scrollable content */}
</div>
)
}
Requirements Validation
These hooks implement the following requirements from the specification:
- Requirement 6.4: Filter state management and clearing
- Requirement 7.3: Visual feedback for user actions (optimistic updates)
- Requirement 7.4: Scroll position preservation
- Requirement 8.3: Search clearing and restoration
Best Practices
- Search Debouncing: Use appropriate debounce times (300ms is a good default)
- Filter State: Keep filter state in sync with URL params for shareable links
- Optimistic Updates: Always provide rollback on error
- Scroll Position: Use unique keys for different views to avoid conflicts
- Error Handling: Always handle errors in async callbacks
Testing
These hooks are designed to be testable with React Testing Library:
import { renderHook, act } from '@testing-library/react'
import { useSearch } from '@/hooks'
test('useSearch debounces queries', async () => {
const onSearch = jest.fn()
const { result } = renderHook(() => useSearch({ onSearch, debounceMs: 100 }))
act(() => {
result.current.setQuery('test')
})
expect(onSearch).not.toHaveBeenCalled()
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 150))
})
expect(onSearch).toHaveBeenCalledWith('test')
})