Files
Todo/hooks/useOptimisticUpdate.ts
Paul Bailey e2e3581d06 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
2025-12-01 20:11:41 +00:00

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
}
}