mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
- Create asyncDataSlice.ts (426 lines)
* AsyncRequest interface for tracking request state
* AsyncDataState interface for global async state
* Four async thunks: fetchAsyncData, mutateAsyncData, refetchAsyncData, cleanupAsyncRequests
* Nine reducers for request state management
* Nine selectors for state access
* Automatic cleanup of old requests (>5min)
* Request deduplication via stable IDs
- Create redux/hooks-async workspace (1300+ lines)
* useReduxAsyncData hook: drop-in replacement for useQuery
- Automatic retries with configurable backoff
- Refetch on focus and refetch interval support
- Success/error callbacks
- Manual retry and refetch functions
* useReduxMutation hook: drop-in replacement for useMutation
- Execute mutations with loading/error tracking
- Status lifecycle tracking
- Multi-step mutation support for complex workflows
* useReduxPaginatedAsyncData: pagination helper
* useReduxMultiMutation: sequential mutation execution
- Create comprehensive unit tests (350+ lines)
* Test data fetching and state updates
* Test error handling and retries
* Test callbacks and status changes
* Test manual refetch/retry operations
* Test pagination functionality
* Full TypeScript type coverage
- Update root package.json to register redux/hooks-async workspace
- Create TANSTACK_TO_REDUX_MIGRATION_CHECKLIST.txt
* Tracks all 25 migration tasks across 5 phases
* Phase 1 now 100% complete
## Implementation Details
All async state stored in Redux, observable via DevTools:
- Requests tracked by ID for deduplication
- Automatic cleanup prevents memory leaks
- Status: idle → pending → succeeded/failed
- Refetching without clearing stale data
- Full TypeScript generic support
No breaking changes - API identical to previous hooks.
## Next Steps
Phase 2: Update api-clients to delegate to Redux hooks
Phase 3: Remove TanStack from providers and package.json
Phase 4: Validation & testing
Phase 5: Documentation updates
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
189 lines
4.8 KiB
Markdown
189 lines
4.8 KiB
Markdown
# @metabuilder/hooks-async
|
|
|
|
Redux-backed async data and mutation hooks - drop-in replacement for TanStack React Query.
|
|
|
|
## Features
|
|
|
|
- ✅ **API Compatible** - Same interfaces as `useQuery`/`useMutation`
|
|
- ✅ **Redux-Backed** - All state in Redux store, observable via DevTools
|
|
- ✅ **Request Deduplication** - Prevents duplicate concurrent requests
|
|
- ✅ **Auto Cleanup** - Old requests removed automatically (>5min)
|
|
- ✅ **Retry Logic** - Automatic retries with configurable backoff
|
|
- ✅ **Refetch Support** - Manual refetch, refetch on focus, auto-refetch intervals
|
|
- ✅ **Pagination** - Built-in pagination helper
|
|
- ✅ **Multi-Step Mutations** - Execute sequences of mutations
|
|
|
|
## Hooks
|
|
|
|
### `useReduxAsyncData`
|
|
|
|
Fetch data with automatic caching and retry logic.
|
|
|
|
```typescript
|
|
import { useReduxAsyncData } from '@metabuilder/hooks-async'
|
|
|
|
function UserProfile() {
|
|
const { data, isLoading, error, refetch } = useReduxAsyncData(
|
|
async () => {
|
|
const res = await fetch('/api/user')
|
|
return res.json()
|
|
},
|
|
{
|
|
maxRetries: 3,
|
|
retryDelay: 1000,
|
|
onSuccess: (data) => console.log('User loaded:', data),
|
|
onError: (error) => console.error('Failed:', error),
|
|
refetchOnFocus: true,
|
|
}
|
|
)
|
|
|
|
if (isLoading) return <div>Loading...</div>
|
|
if (error) return <div>Error: {error}</div>
|
|
return <div>{data?.name}</div>
|
|
}
|
|
```
|
|
|
|
### `useReduxMutation`
|
|
|
|
Execute write operations (POST, PUT, DELETE).
|
|
|
|
```typescript
|
|
import { useReduxMutation } from '@metabuilder/hooks-async'
|
|
|
|
function CreateUserForm() {
|
|
const { mutate, isLoading, error } = useReduxMutation(
|
|
async (userData) => {
|
|
const res = await fetch('/api/users', {
|
|
method: 'POST',
|
|
body: JSON.stringify(userData),
|
|
})
|
|
return res.json()
|
|
},
|
|
{
|
|
onSuccess: (user) => console.log('User created:', user),
|
|
onError: (error) => console.error('Failed:', error),
|
|
}
|
|
)
|
|
|
|
const handleSubmit = async (formData) => {
|
|
try {
|
|
const user = await mutate(formData)
|
|
console.log('Created:', user)
|
|
} catch (err) {
|
|
console.error(err)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={(e) => {
|
|
e.preventDefault()
|
|
handleSubmit(new FormData(e.target))
|
|
}}>
|
|
{/* form fields */}
|
|
</form>
|
|
)
|
|
}
|
|
```
|
|
|
|
### `useReduxPaginatedAsyncData`
|
|
|
|
Fetch paginated data with built-in pagination controls.
|
|
|
|
```typescript
|
|
const { data, currentPage, nextPage, prevPage, isLoading } =
|
|
useReduxPaginatedAsyncData(
|
|
(page, pageSize) => fetchUsers(page, pageSize),
|
|
{ pageSize: 20 }
|
|
)
|
|
```
|
|
|
|
## Architecture
|
|
|
|
```
|
|
redux/hooks-async/
|
|
├── src/
|
|
│ ├── useReduxAsyncData.ts # Primary async hook + pagination
|
|
│ ├── useReduxMutation.ts # Mutation hook + multi-step mutations
|
|
│ ├── index.ts # Public exports
|
|
│ └── __tests__/
|
|
│ ├── useReduxAsyncData.test.ts
|
|
│ └── useReduxMutation.test.ts
|
|
├── package.json # Dependencies: Redux, React
|
|
├── tsconfig.json # TypeScript config
|
|
└── README.md # This file
|
|
```
|
|
|
|
## State Shape
|
|
|
|
All async state is stored in Redux:
|
|
|
|
```typescript
|
|
// Redux State
|
|
{
|
|
asyncData: {
|
|
requests: {
|
|
[requestId]: {
|
|
id: string
|
|
status: 'idle' | 'pending' | 'succeeded' | 'failed'
|
|
data: unknown
|
|
error: string | null
|
|
retryCount: number
|
|
maxRetries: number
|
|
lastRefetch: number
|
|
refetchInterval: number | null
|
|
isRefetching: boolean
|
|
}
|
|
},
|
|
globalLoading: boolean
|
|
globalError: string | null
|
|
}
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
```bash
|
|
npm run test --workspace=@metabuilder/hooks-async
|
|
npm run typecheck --workspace=@metabuilder/hooks-async
|
|
npm run build --workspace=@metabuilder/hooks-async
|
|
```
|
|
|
|
## Migration from TanStack
|
|
|
|
Replace imports:
|
|
|
|
```typescript
|
|
// Before
|
|
import { useQuery, useMutation } from '@tanstack/react-query'
|
|
|
|
// After
|
|
import { useReduxAsyncData, useReduxMutation } from '@metabuilder/hooks-async'
|
|
|
|
// Use identically
|
|
const query = useReduxAsyncData(fetchFn, options)
|
|
const mutation = useReduxMutation(mutateFn, options)
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
- **Request Deduplication**: Same requestId = same cache entry
|
|
- **Memory Management**: Old requests (>5min) auto-cleanup
|
|
- **DevTools**: Full Redux DevTools support for debugging
|
|
- **Selector Memoization**: Use selectors for efficient re-renders
|
|
|
|
## Error Handling
|
|
|
|
Errors are stored in Redux and available via:
|
|
|
|
```typescript
|
|
const { error } = useReduxAsyncData(fetchFn)
|
|
// Access via selector
|
|
const error = useSelector((s) => selectAsyncError(s, requestId))
|
|
```
|
|
|
|
## References
|
|
|
|
- [asyncDataSlice.ts](../slices/src/slices/asyncDataSlice.ts) - Redux slice
|
|
- [TANSTACK_TO_REDUX_MIGRATION_CHECKLIST.txt](../../txt/TANSTACK_TO_REDUX_MIGRATION_CHECKLIST.txt) - Full migration plan
|
|
- [CLAUDE.md](../../CLAUDE.md) - Project guidelines
|