Files
metabuilder/redux/hooks-async
johndoe6345789 9a6d93ef44 fix(deps): HIGH priority testing library and Storybook standardization
All HIGH priority fixes from comprehensive dependency audit (Jan 23, 2026):

Testing Libraries (4 packages):
- pastebin: @testing-library/react v14 → v16, jest-dom 6.1 → 6.6
- redux/hooks-async: @testing-library/react v14 → v16 (added), jest-dom 6.6 (added)
- workflow: jest 29.0.0 → 29.7.0
- codegen/spark-tools: vitest 3.0.9 → 4.0.16

Storybook Configuration (2 packages):
- storybook: Standardized addon versions (react-vite, test, essentials, interactions all 8.6.15)
  - Fixed React type mismatch (@types/react 19 → 18 to match runtime 18.3.1)
- workflowui: Fixed React type mismatch (@types/react 19 → 18, @types/react-dom 19 → 18)

Verification: npm install succeeds (1197 packages, 0 vulnerabilities)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-24 00:13:25 +00:00
..

@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.

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).

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.

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:

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

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:

// 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:

const { error } = useReduxAsyncData(fetchFn)
// Access via selector
const error = useSelector((s) => selectAsyncError(s, requestId))

References