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:
138
hooks/useOptimisticUpdate.ts
Normal file
138
hooks/useOptimisticUpdate.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
'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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user