Skip to content

Runnable React request example

This component uses a stable Fetcher instance and the real useFetcher hook. It renders the hook's idle, loading, success, and error states; abort() returns an active request to idle, so the example does not invent a separate cancelled state.

tsx
/*
 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Fetcher, ResultExtractors } from '@ahoo-wang/fetcher';
import { useFetcher } from '@ahoo-wang/fetcher-react';
import { useMemo } from 'react';

interface User {
  id: string;
  name: string;
}

type Result = User[] | { status: string };

export function ReactRequests({ baseURL = '/api' }: { baseURL?: string }) {
  const fetcher = useMemo(() => new Fetcher({ baseURL }), [baseURL]);
  const request = useFetcher<Result>({
    fetcher,
    resultExtractor: ResultExtractors.Json,
  });

  const output = request.loading
    ? request.status
    : request.error
      ? `Error · ${request.error.name}`
      : Array.isArray(request.result)
        ? request.result.map(user => user.name).join(', ')
        : (request.result?.status ?? request.status);

  return (
    <section
      aria-label="React requests"
      style={{ display: 'grid', gap: '0.75rem', maxWidth: '24rem' }}
    >
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
        <button onClick={() => void request.execute({ url: '/users' })}>
          Load
        </button>
        <button onClick={() => void request.execute({ url: '/error' })}>
          Fail
        </button>
        <button onClick={() => void request.execute({ url: '/slow' })}>
          Load slow
        </button>
        <button disabled={!request.loading} onClick={request.abort}>
          Cancel
        </button>
      </div>
      <output aria-live="polite">{output}</output>
    </section>
  );
}

execute() resolves to void; read data from result. reset() only clears state, while abort() also invalidates and aborts the active request. See the Fetcher hook reference and the React integration task guide.

Run the verified fixture

Contributors need Node >=20.20.2, pnpm 10.34.5, and the repository dependencies installed. From the repository root, run:

bash
pnpm exec vitest run --project=storybook stories/docs/ReactRequests.test.stories.tsx

To try the same interactions manually, run pnpm storybook, open the printed local URL, and select Docs / React requests in the sidebar. The repository Storybook is the no-backend runnable example.

The Storybook fixture intercepts fetch for https://api.example.test: /users returns Ada and Lin, /error returns HTTP 500, and the documentation's /slow request resolves after 2000 ms unless its AbortSignal fires. Other existing stories keep the fixture's 80 ms default. Each story restores the previous global fetch afterward. The play tests assert the rendered result or error, then wait past the slow response before confirming that an aborted request did not publish a late success. This fixture proves browser-side state transitions; it does not prove compatibility with your API.

Use it in a Vite application

The consumer setup below assumes an existing React + TypeScript Vite application. This repository is validated with Node >=20.20.2, pnpm 10.34.5, Vite ^8.2.2, TypeScript ^6.0.3, and React/React DOM ^19.2.8. The published Fetcher packages themselves declare Node >=18.20.8.

Install the React package and its declared peer package graph explicitly:

bash
pnpm add @ahoo-wang/fetcher@^5.0.0 \
  @ahoo-wang/fetcher-react@^5.0.0 \
  @ahoo-wang/fetcher-eventstream@^5.0.0 \
  @ahoo-wang/fetcher-eventbus@^5.0.0 \
  @ahoo-wang/fetcher-storage@^5.0.0 \
  @ahoo-wang/fetcher-wow@^5.0.0 \
  @ahoo-wang/fetcher-decorator@^5.0.0 \
  @ahoo-wang/fetcher-cosec@^5.0.0 \
  react@^19.2.8 react-dom@^19.2.8

This section connects an existing Vite application to an existing API; it does not install or create a backend. Copy the component above to src/ReactRequests.tsx. Its default base URL is /api; point that path at a backend with these demo routes, or pass another baseURL:

RouteResponse used by the component
GET /usersHTTP 200 JSON array such as [{ "id": "u-ada", "name": "Ada" }]
GET /errorAny non-2xx response; the repository fixture uses HTTP 500 with { "message": "Fixture server error" }
GET /slowA delayed HTTP 200 JSON object such as { "status": "completed" }; the server must observe request cancellation if it should stop work

Then replace the Vite entry file src/main.tsx with:

tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { ReactRequests } from './ReactRequests';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ReactRequests />
  </StrictMode>,
);

Keep Vite's generated index.html, then run pnpm dev and open the URL it prints. Cancellation can stop browser-side work only when the underlying operation observes the AbortSignal; it cannot roll back a request the server already applied.

Released under the Apache License 2.0.