- 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
139 lines
3.6 KiB
TypeScript
139 lines
3.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useCallback, useTransition } from 'react'
|
|
|
|
interface UseOptimisticUpdateOptions<T> {
|
|
onUpdate: (data: T) => Promise<void>
|
|
onError?: (error: Error) => void
|
|
}
|
|
|
|
interface UseOptimisticUpdateReturn<T> {
|
|
optimisticData: T | null
|
|
isPending: boolean
|
|
error: Error | null
|
|
performUpdate: (data: T) => Promise<void>
|
|
reset: () => void
|
|
}
|
|
|
|
/**
|
|
* Custom hook for optimistic UI updates
|
|
* Provides immediate UI feedback while server action is processing
|
|
* Validates: Requirements 7.3
|
|
*/
|
|
export function useOptimisticUpdate<T>(
|
|
options: UseOptimisticUpdateOptions<T>
|
|
): UseOptimisticUpdateReturn<T> {
|
|
const { onUpdate, onError } = options
|
|
|
|
const [isPending, startTransition] = useTransition()
|
|
const [optimisticData, setOptimisticData] = useState<T | null>(null)
|
|
const [error, setError] = useState<Error | null>(null)
|
|
|
|
const performUpdate = useCallback(async (data: T) => {
|
|
// Clear any previous errors
|
|
setError(null)
|
|
|
|
// Set optimistic data immediately for instant UI feedback
|
|
setOptimisticData(data)
|
|
|
|
// Perform the actual update in a transition
|
|
startTransition(async () => {
|
|
try {
|
|
await onUpdate(data)
|
|
// Clear optimistic data after successful update
|
|
setOptimisticData(null)
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error('Update failed')
|
|
setError(error)
|
|
// Clear optimistic data on error
|
|
setOptimisticData(null)
|
|
|
|
if (onError) {
|
|
onError(error)
|
|
}
|
|
}
|
|
})
|
|
}, [onUpdate, onError])
|
|
|
|
const reset = useCallback(() => {
|
|
setOptimisticData(null)
|
|
setError(null)
|
|
}, [])
|
|
|
|
return {
|
|
optimisticData,
|
|
isPending,
|
|
error,
|
|
performUpdate,
|
|
reset
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simplified hook for optimistic list updates
|
|
* Useful for managing lists of items with optimistic updates
|
|
*/
|
|
export function useOptimisticList<T extends { id: string }>(
|
|
initialData: T[],
|
|
onUpdate: (data: T[]) => Promise<void>
|
|
) {
|
|
const [optimisticList, setOptimisticList] = useState<T[]>(initialData)
|
|
const [isPending, startTransition] = useTransition()
|
|
|
|
const addItem = useCallback((item: T) => {
|
|
setOptimisticList(prev => [...prev, item])
|
|
|
|
startTransition(async () => {
|
|
try {
|
|
await onUpdate([...optimisticList, item])
|
|
} catch (error) {
|
|
// Rollback on error
|
|
setOptimisticList(initialData)
|
|
console.error('Failed to add item:', error)
|
|
}
|
|
})
|
|
}, [optimisticList, initialData, onUpdate])
|
|
|
|
const updateItem = useCallback((id: string, updates: Partial<T>) => {
|
|
setOptimisticList(prev =>
|
|
prev.map(item => (item.id === id ? { ...item, ...updates } : item))
|
|
)
|
|
|
|
startTransition(async () => {
|
|
try {
|
|
const updatedList = optimisticList.map(item =>
|
|
item.id === id ? { ...item, ...updates } : item
|
|
)
|
|
await onUpdate(updatedList)
|
|
} catch (error) {
|
|
// Rollback on error
|
|
setOptimisticList(initialData)
|
|
console.error('Failed to update item:', error)
|
|
}
|
|
})
|
|
}, [optimisticList, initialData, onUpdate])
|
|
|
|
const removeItem = useCallback((id: string) => {
|
|
setOptimisticList(prev => prev.filter(item => item.id !== id))
|
|
|
|
startTransition(async () => {
|
|
try {
|
|
const updatedList = optimisticList.filter(item => item.id !== id)
|
|
await onUpdate(updatedList)
|
|
} catch (error) {
|
|
// Rollback on error
|
|
setOptimisticList(initialData)
|
|
console.error('Failed to remove item:', error)
|
|
}
|
|
})
|
|
}, [optimisticList, initialData, onUpdate])
|
|
|
|
return {
|
|
optimisticList,
|
|
isPending,
|
|
addItem,
|
|
updateItem,
|
|
removeItem
|
|
}
|
|
}
|