npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@beyond.dev/openapi-react

v0.1.4

Published

Suspenseful React hooks for openapi-fetch.

Readme

@beyond.dev/openapi-react

Fetch, cache, and mutate data from OpenAPI-typed services with full type safety.

Built on openapi-fetch. Every path, parameter, and response is inferred from your OpenAPI schema — no manual typing.

Install

npm install @beyond.dev/openapi-react openapi-fetch openapi-typescript-helpers

Quick Start

Generate types from your OpenAPI spec (see openapi-typescript), then create a client:

import { createClient } from "@beyond.dev/openapi-react";
import type { paths } from "./api.d.ts"; // generated by openapi-typescript

export const api = createClient<paths>({
  baseUrl: "https://api.example.com",
});

Fetch data in a component with useLoader (requires a <Suspense> boundary):

import { Suspense } from "react";
import { api } from "./api";

function UserList() {
  const { data } = api.useLoader({
    path: "GET /users",
    input: { query: { limit: 20 } },
  });

  return <ul>{data.items.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <UserList />
    </Suspense>
  );
}

Hooks

useLoader

Cached GET with Suspense support. Suspends while loading, throws on error.

const { data, status, fetchStatus, invalidate, refetch } = api.useLoader({
  path: "GET /users/{id}",
  input: { path: { id: "u_123" } },
  staleTime: 5000, // ms before re-fetching (default: 1000)
  disabled: false, // skip fetch entirely
  refetchOnMount: true, // re-fetch when component mounts (default: true)
  refetchOnFocus: true, // re-fetch on window focus (default: true)
  refetchOnReconnect: true, // re-fetch on network reconnect (default: true)
  refetchInterval: 30000, // poll every N ms, or (data) => ms | false
});

| Field | Type | Description | | -------------- | ------------------------------------------------------------------ | --------------------------------------- | | data | T | Response data | | status | "success" \| "error" \| "disabled" | Logical render state (see note below) | | fetchStatus | "fetching" \| "refetching" \| "success" \| "error" \| "uncached" | Raw network state (see note below) | | invalidate() | () => void | Mark stale, triggers background refetch | | refetch() | () => Promise<CachedResponse> | Force immediate refetch |

status vs fetchStatus

status is the logical render state — it collapses "refetching" into "success" (stale data stays visible during a background refresh) and is what you branch on in JSX. fetchStatus is the raw network state and exposes "refetching" separately, useful for showing a subtle background-refresh indicator without hiding existing data. When disabled: true, fetchStatus is "uncached".

// Branch on status for rendering decisions:
if (result.status === "error") return <ErrorMessage />;

// Use fetchStatus to layer a background-refresh hint over existing data:
{
  result.fetchStatus === "refetching" && <Spinner size="sm" />;
}
{
  result.data && <UserList users={result.data} />;
}

useInlineLoader

Same as useLoader but without Suspense. Returns a discriminated union instead.

const result = api.useInlineLoader({
  path: "GET /users/{id}",
  input: { path: { id: "u_123" } },
});

if (result.status === "fetching") return <Spinner />;
if (result.status === "error") return <Error data={result.error} />;
return <User data={result.data} />;

The "success" variant includes lastError — the previous error if a refetch succeeds after a failure.

useAction

Uncached mutations. Returns a send function and the current status.

const { send, status } = api.useAction({
  path: "POST /users",
  onSuccess(data, response) {/* redirect, update cache, etc. */},
  onError(error, response) {/* show toast, etc. */},
  onSettled(data, error, response) {/* runs on both success and error */},
});

// Call send with typed input
const user = await send({
  body: { name: "Alice", email: "[email protected]" },
});

| Field | Type | Description | | -------- | ---------------------------------------------- | ---------------------- | | send | (input, requestInit?) => Promise<T> | Execute the request | | status | "idle" \| "fetching" \| "success" \| "error" | Current mutation state |

send throws ErrorResponse on failure. The onError callback receives the typed error data.

onSettled(data, error, response) runs after every send regardless of outcome — useful for cleanup that must always happen (closing a modal, resetting a form). data is defined on success; error is defined on failure.

Cache Operations

load

Fetch programmatically. Respects staleTime — returns cached data if fresh.

const cached = await api.load({
  path: "GET /users",
  staleTime: 0, // force refetch
});

hydrate

Pre-populate the cache without a network request.

Note: hydrate is a no-op when the cache already holds a successful entry for that key. To overwrite existing data, call purge first then hydrate.

api.hydrate({
  path: "GET /users/{id}",
  data: { id: "u_123", name: "Alice" },
  input: { path: { id: "u_123" } },
});

invalidate

Mark a cache entry stale. Components subscribed to that key will refetch in the background.

// Exact match
api.invalidate({ path: "GET /users/{id}", input: { path: { id: "u_123" } } });

// Pattern match — invalidate all /users entries
api.invalidate({
  match({ path }) {
    return path.startsWith("GET /users");
  },
});

refetch

Invalidate and immediately re-fetch.

await api.refetch({ path: "GET /users" });

// Pattern match
await api.refetch({
  match({ path }, mountCount) {
    return path === "GET /users" && mountCount > 0;
  },
});

The match callback receives the parsed cache key and the current mount count (refCount).

purge

Remove entries from the cache entirely.

api.purge({ path: "GET /users/{id}", input: { path: { id: "u_123" } } });

url

Build a typed URL string from a path and parameters.

const href = api.url({
  path: "GET /users/{id}",
  input: { path: { id: "u_123" }, query: { expand: "roles" } },
});
// "https://api.example.com/users/u_123?expand=roles"

Configuration

const api = createClient<paths>({
  baseUrl: "https://api.example.com",

  // How long cached data is considered fresh (ms). Default: 1000.
  staleTime: 5000,

  // How long to keep unused cache entries after all subscribers unmount (ms). Default: 300_000.
  cacheTime: 60_000,

  // Max retries on 5xx errors. Default: 3.
  retries: 2,

  // Custom retry predicate. Return false to abort, true/void to retry.
  shouldRetry(error, retryCount) {
    return error.response?.status !== 429;
  },

  // Transform all responses before caching.
  transform: camelize,

  // Default request options (credentials, mode, headers).
  requestInit: () => ({
    credentials: "include",
    headers: { "X-App-Version": "1.0" },
  }),

  // Custom query string serializer.
  querySerializer: createQuerySerializer({ array: { style: "form" } }),

  // Extend cache keys — useful for user-scoped caches.
  extendCacheKey: ({ path, input }) => ({ path, input, userId: getUser().id }),

  // Global callbacks.
  onEachSuccess(data) {},
  onEachError(error) {},

  debug: true, // Log requests and responses to the console.
});

Utilities

camelize

Recursively converts snake_case keys to camelCase.

import { camelize } from "@beyond.dev/openapi-react";

camelize({ user_id: "u_1", created_at: "2024-01-01" });
// => { userId: "u_1", createdAt: "2024-01-01" }

Use as a global transform to normalize API responses across all hooks:

const api = createClient<paths>({ baseUrl, transform: camelize });

snakenize

Shallow converts camelCase keys to snake_case (top-level keys only).

import { snakenize } from "@beyond.dev/openapi-react";

snakenize({ userId: "u_1", firstName: "Alice" });
// => { user_id: "u_1", first_name: "Alice" }

Use it when building request bodies that expect snake_case from camelCase inputs.

Error Handling

Failed requests throw ErrorResponse<T>, which carries the typed error payload.

import { ErrorResponse } from "@beyond.dev/openapi-react";

try {
  await api.load({ path: "GET /users/{id}", input: { path: { id: "x" } } });
} catch (err) {
  if (err instanceof ErrorResponse) {
    console.log(err.data); // typed error body
    console.log(err.response); // raw Response
  }
}

With useLoader (Suspense mode), errors propagate to the nearest error boundary. With useInlineLoader, they surface as status: "error" with result.error typed to your schema's error shape.