Skip to content

@ahoo-wang/fetcher-react

The @ahoo-wang/fetcher-react package provides a comprehensive set of React hooks that wrap the Fetcher HTTP client ecosystem. It delivers declarative data fetching with automatic loading/error state management, race condition protection, abort controller integration, debounced queries, and deep integration with Wow, CoSec, EventBus, and Storage.

Installation

bash
pnpm add @ahoo-wang/fetcher-react

Module Structure

mermaid
graph TB
    subgraph sg_1 ["fetcher-react modules"]
        direction TB
        CORE["core/<br>Base hooks"]
        FETCHER["fetcher/<br>Fetcher integration"]
        API["api/<br>Decorator-based API hooks"]
        WOW["wow/<br>Wow CQRS query hooks"]
        COSEC["cosec/<br>Security hooks"]
        STORAGE["storage/<br>Storage hooks"]
        EVENTBUS["eventbus/<br>Event subscription hooks"]
        DATAMON["dataMonitor/<br>Data monitor hooks"]
    end

    CORE --> FETCHER
    CORE --> API
    FETCHER --> WOW
    CORE --> COSEC
    CORE --> STORAGE
    CORE --> EVENTBUS
    CORE --> DATAMON

    style CORE fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style FETCHER fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style API fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style WOW fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style COSEC fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style STORAGE fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style EVENTBUS fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style DATAMON fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

Source: packages/react/src/index.ts

Core Hooks

usePromiseState

The foundation hook for managing promise lifecycle state without execution logic. Tracks idle, loading, success, and error states.

typescript
import { usePromiseState, PromiseStatus } from '@ahoo-wang/fetcher-react';

function MyComponent() {
  const { status, loading, result, error, setSuccess, setError, setIdle } =
    usePromiseState<string>();

  return (
    <div>
      <p>Status: {status}</p>
      {loading && <p>Loading...</p>}
      {result && <p>Result: {result}</p>}
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}
PropertyTypeDescription
statusPromiseStatusCurrent state (idle, loading, success, error)
loadingbooleantrue when status is loading
resultR | undefinedThe resolved value
errorE | undefinedThe error if rejected
setLoading()() => voidTransition to loading state
setSuccess(result)(result: R) => Promise<void>Transition to success with callback
setError(error)(error: E) => Promise<void>Transition to error with callback
setIdle()() => voidReset to idle state

Source: packages/react/src/core/usePromiseState.ts:119-190

useExecutePromise

Extends usePromiseState with promise execution, automatic abort support, race condition protection via request IDs, and mount-safety checks.

typescript
import { useExecutePromise } from '@ahoo-wang/fetcher-react';

function DataFetcher() {
  const { loading, result, error, execute, reset, abort } =
    useExecutePromise<string>();

  const fetch = () =>
    execute(async (abortController) => {
      const response = await fetch('/api/data', { signal: abortController.signal });
      return response.text();
    });

  return (
    <div>
      <button onClick={fetch}>Fetch</button>
      <button onClick={abort}>Cancel</button>
      {loading && <p>Loading...</p>}
      {result && <p>{result}</p>}
    </div>
  );
}
mermaid
stateDiagram-v2
    [*] --> IDLE
    IDLE --> LOADING : execute()
    LOADING --> SUCCESS : promise resolves
    LOADING --> ERROR : promise rejects
    LOADING --> IDLE : AbortError / abort()
    SUCCESS --> LOADING : execute()
    ERROR --> LOADING : execute()
    SUCCESS --> IDLE : reset()
    ERROR --> IDLE : reset()
    LOADING --> IDLE : unmount cleanup

Key features:

  • Auto-abort -- cancels any in-flight request before starting a new one
  • Manual abort -- dedicated abort() method with onAbort callback
  • Race condition protection -- request IDs prevent stale updates
  • Mount safety -- no state updates after unmount

Source: packages/react/src/core/useExecutePromise.ts:210-334

useQuery

Combines useExecutePromise with query parameter management. Supports auto-execution when query parameters change.

typescript
import { useQuery } from '@ahoo-wang/fetcher-react';

function UserComponent() {
  const { loading, result, execute, setQuery } = useQuery<UserQuery, User>({
    initialQuery: { id: '1' },
    execute: async (query) => {
      const res = await fetch(`/api/users/${query.id}`);
      return res.json();
    },
    autoExecute: true,
  });

  const handleChange = (userId: string) => {
    setQuery({ id: userId }); // auto-executes when autoExecute is true
  };

  return <div>{loading ? <p>Loading...</p> : <p>{result?.name}</p>}</div>;
}

Source: packages/react/src/core/useQuery.ts:105-173

Fetcher Integration Hooks

useFetcher

The primary hook for HTTP fetch operations. Wraps the Fetcher client with automatic abort, race condition protection, and comprehensive state management.

mermaid
sequenceDiagram
autonumber

    participant C as Component
    participant UF as useFetcher
    participant EP as useExecutePromise
    participant F as Fetcher
    participant API as HTTP API

    C->>UF: execute(request)
    UF->>EP: execute(supplier)
    EP->>EP: Abort previous request
    EP->>EP: Create new AbortController
    UF->>F: fetcher.exchange(request, options)
    F->>API: HTTP request
    API-->>F: HTTP response
    F-->>UF: FetchExchange
    UF->>UF: exchange.extractResult()
    UF-->>C: { loading, result, error, exchange }
typescript
import { useFetcher } from '@ahoo-wang/fetcher-react';
import { ResultExtractors } from '@ahoo-wang/fetcher';

function UserProfile({ userId }: { userId: string }) {
  const { loading, result, error, execute } = useFetcher<User>({
    resultExtractor: ResultExtractors.Json,
  });

  useEffect(() => {
    execute({ url: `/api/users/${userId}`, method: 'GET' });
  }, [userId]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return <p>{result?.name}</p>;
}
PropertyTypeDescription
exchangeFetchExchange | undefinedCurrent/recent fetch exchange with request/response details
execute(request)(request: FetchRequest) => Promise<void>Execute a fetch request with auto-abort
Inherits from useExecutePromise--loading, result, error, status, reset, abort

Source: packages/react/src/fetcher/useFetcher.ts:162-226

useFetcherQuery

A higher-level hook that combines useFetcher with POST-based query semantics. Sends query parameters as the request body and supports auto-execution.

typescript
import { useFetcherQuery } from '@ahoo-wang/fetcher-react';

interface SearchQuery { keyword: string; limit: number; }
interface SearchResult { items: Item[]; total: number; }

function SearchComponent() {
  const { loading, result, execute, setQuery } = useFetcherQuery<SearchQuery, SearchResult>({
    url: '/api/search',
    initialQuery: { keyword: '', limit: 10 },
    autoExecute: false,
  });

  const handleSearch = (keyword: string) => {
    setQuery({ keyword, limit: 10 }); // auto-executes if autoExecute was true
  };

  return <div>{loading ? <p>Searching...</p> : <p>Found {result?.total} items</p>}</div>;
}

Source: packages/react/src/fetcher/useFetcherQuery.ts:125-192

Debounced Variants

For search-as-you-type scenarios, debounced variants delay execution until input stabilizes:

HookBase HookDescription
useDebouncedCallback--Debounces any callback by a configurable delay
useDebouncedExecutePromiseuseExecutePromiseDebounced promise execution
useDebouncedQueryuseQueryDebounced query with auto-execution
useDebouncedFetcheruseFetcherDebounced fetcher execution
useDebouncedFetcherQueryuseFetcherQueryDebounced POST query

Source: packages/react/src/core/debounced/, packages/react/src/fetcher/debounced/ (useDebouncedFetcher, useDebouncedFetcherQuery)

API Hooks Generation

Instead of writing useExecutePromise / useQuery wrappers manually for each API method, use the factory functions to auto-generate type-safe hooks from any decorated API service class. These factories introspect the class, collect all promise-returning methods, and create a useXxx hook for each — with full parameter and return type inference.

FactoryHook BaseUse Case
createExecuteApiHooksuseExecutePromiseMutation/command methods (POST, PUT, DELETE) — manual trigger
createQueryApiHooksuseQueryQuery methods (GET) — auto-execute on mount/param change

Creating Execute Hooks (Mutations)

typescript
import { createExecuteApiHooks } from '@ahoo-wang/fetcher-react';
import { UserService } from './UserService'; // decorated with @api

// Pass fetcher via ApiMetadata to the decorated API class constructor
const userService = new UserService({ fetcher });

// Auto-generates useCreateUser, useUpdateUser, useDeleteUser...
const userExecuteHooks = createExecuteApiHooks({ api: userService });

// In a component — full type inference for params and return
function CreateUserForm() {
  const { result, loading, error, execute } = userExecuteHooks.useCreateUser();

  const handleSubmit = async (data: UserDTO) => {
    // params are type-checked against the method signature
    await execute(data);
  };

  return <button onClick={() => handleSubmit({ name: 'Alice' })}>Create</button>;
}

Creating Query Hooks (Reads)

typescript
import { createQueryApiHooks } from '@ahoo-wang/fetcher-react';

// Auto-generates useGetUser, useListUsers, useSearchUsers...
const userQueryHooks = createQueryApiHooks({ api: new UserService({ fetcher }) });

function UserProfile({ userId }: { userId: string }) {
  // Pass query params via query — auto-executes on mount and re-executes when userId changes
  const { result: user, loading, error } = userQueryHooks.useGetUser({ query: { id: userId } });

  if (loading) return <Spinner />;
  if (error) return <ErrorView error={error} />;
  return <div>{user?.name}</div>;
}

Naming Convention

methodNameToHookName converts method names to hook names by prepending use and capitalizing:

Method NameGenerated Hook
getUseruseGetUser
createPostuseCreatePost
deleteByIduseDeleteById

Source: packages/react/src/api/

Wow CQRS Hooks

For applications using the Wow framework, dedicated hooks provide typed access to aggregate query operations:

mermaid
graph LR
    subgraph sg_1 ["Wow Query Hooks"]
        direction LR
        SQ["useSingleQuery"]
        LQ["useListQuery"]
        LSTQ["useListStreamQuery"]
        PQ["usePagedQuery"]
        CQ["useCountQuery"]
    end

    SQ --> |"single state"| SNAPSHOT["SnapshotQueryClient"]
    LQ --> |"list states"| SNAPSHOT
    LSTQ --> |"SSE stream"| SNAPSHOT
    PQ --> |"paged query"| SNAPSHOT
    CQ --> |"count"| SNAPSHOT

    style SQ fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style LQ fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style LSTQ fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style PQ fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CQ fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style SNAPSHOT fill:#161b22,stroke:#30363d,color:#e6edf3
HookDescription
useSingleQueryFetch a single aggregate snapshot
useListQueryFetch a list of aggregate snapshots
useListStreamQueryFetch aggregate snapshots as an SSE stream
usePagedQueryFetch paginated aggregate snapshots
useCountQueryCount aggregates matching a condition

Fetcher-based variants (useFetcherSingleQuery, useFetcherListQuery, etc.) combine these with the fetcher hook for direct HTTP integration.

Source: packages/react/src/wow/

CoSec Integration

Security-related hooks for applications using CoSec authentication:

Hook / ComponentDescription
useSecurityAccess current authentication state (authenticated, currentUser)
SecurityContextReact context providing CoSec security state to child components
RouteGuardConditionally renders children (when authenticated) or fallback; optional onUnauthorized callback for redirect wiring
RefreshableRouteGuardRoute guard that attempts token refresh before redirecting

Source: packages/react/src/cosec/

Storage Hooks

React bindings for the Storage package:

HookDescription
useKeyStorage<T>Reactive binding to a KeyStorage<T> instance. Returns a [value, setValue, remove] tuple. Auto-re-renders on storage changes.
useImmerKeyStorage<T>Like useKeyStorage but accepts Immer-style draft mutations. Returns [value, updateImmer, remove].

Source: packages/react/src/storage/

EventBus Integration

HookDescription
useEventSubscriptionSubscribe to typed events from an event bus with automatic cleanup on unmount

Source: packages/react/src/eventbus/useEventSubscription.ts

Notification System

A channel-based notification system:

  • NotificationCenter -- manages notification dispatching
  • BrowserNotificationChannel -- shows native browser notifications
  • Extensible via custom NotificationChannel implementations

The exported notificationCenter singleton registers a beforeunload cleanup handler at module scope. The registration is guarded by a typeof window check, so importing the package in Node/SSR environments does not throw.

Source: packages/react/src/notification/, packages/react/src/notification/notificationCenter.ts:65-71

Utility Hooks

HookDescription
useMountedReturns a function that checks if the component is still mounted
useLatest<T>Ref that always holds the latest value (avoids stale closures)
useForceUpdateReturns a function to force a component re-render
useFullscreenManages fullscreen state for container elements

Source: packages/react/src/core/

Key Exports

ExportSourceDescription
usePromiseStatecore/Base state management for promise lifecycle
useExecutePromisecore/Promise execution with abort and race-condition protection
useQuerycore/Query-based async operations with auto-execution
useFetcherfetcher/HTTP fetch operations via Fetcher client
useFetcherQueryfetcher/POST-based query with Fetcher
PromiseStatuscore/Enum: IDLE, LOADING, SUCCESS, ERROR
useKeyStoragestorage/Reactive storage binding
useImmerKeyStoragestorage/Reactive storage binding with Immer-style draft updates
useEventSubscriptioneventbus/Event bus subscription with auto-cleanup

Cross-References

  • Fetcher -- Core HTTP client used by useFetcher and useFetcherQuery
  • Wow -- Wow query hooks (useSingleQuery, usePagedQuery, etc.) target Wow aggregate snapshots
  • CoSec -- useSecurity, RouteGuard hooks consume CoSec authentication state
  • Storage -- useKeyStorage binds reactively to KeyStorage instances
  • EventBus -- useEventSubscription subscribes to typed event buses

Released under the Apache License 2.0.