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

@dyadpy/react

v0.2.5-alpha.0

Published

React hooks (useQuery, useMutation, useSubscription) on top of TanStack Query for Dyadpy-generated clients.

Downloads

1,222

Readme

@dyadpy/react

React hooks for Dyadpy generated clients, built on TanStack Query.

pnpm add @dyadpy/react@alpha @dyadpy/ts@alpha @tanstack/react-query
# alpha channel — drop `@alpha` once v0.1.0 ships

@dyadpy/react adds hook leaves on top of your generated client:

| Hook | Wraps | For | | ----------------- | --------------------------- | ----------------------------------------------------- | | useQuery | useQuery (TanStack) | Unary endpoints. Cache, refetch, invalidate. | | useMutation | useMutation (TanStack) | Endpoints called imperatively (POST, PATCH, etc). | | useSubscription | (custom, async-iter driven) | Streaming endpoints (stream[T] on the Python side). |

Types come straight from the generated nested ApiRoutes interface. Args, hook data, mutation variables, stream events, and the typed error union from @raises(...) are inferred from the generated client. No extra codegen.

Setup

Wrap your app in TanStack's QueryClientProvider as usual, then create the bound hooks once:

// src/lib/dyadpy/hooks.ts
import { createReactClient } from "@dyadpy/react";
import { api, routeMeta } from "./client"; // generated by `dyadpy dev`

export const dyad = createReactClient(api, routeMeta);

If your frontend proxies API requests to avoid CORS, point the generated client at the proxy path before binding React hooks:

// Vite/Next dev proxy or reverse proxy forwards /api/dyadpy -> Python server
import { createReactClient } from "@dyadpy/react";
import { createApi, routeMeta } from "./client";

const api = createApi({ baseUrl: "/api/dyadpy" });
export const dyad = createReactClient(api, routeMeta);

The React Query QueryClient stays normal; transport config belongs to createApi({ baseUrl, headers, fetch }), and every hook leaf uses that same configured client.

// src/main.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

createRoot(document.getElementById("root")!).render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
);

SSR Prefetch

Use @dyadpy/react/server from React Server Components or other server loaders to prefetch into a TanStack QueryClient. Create a request-scoped Dyadpy client so auth headers and the server baseUrl do not leak across requests:

import { dehydrate, QueryClient } from "@tanstack/react-query";
import { prefetchQuery } from "@dyadpy/react/server";
import { forwardHeaders } from "@dyadpy/ts";
import { headers } from "next/headers";

import { createApi, routeMeta } from "./client";
import { createReactClient } from "@dyadpy/react";

const queryClient = new QueryClient();
const api = createApi({
  baseUrl: process.env.DYADPY_API_URL,
  headers: forwardHeaders(await headers()),
});
const dyad = createReactClient(api, routeMeta);

await prefetchQuery(queryClient, dyad.issues.byId, { issueId: 1 });
const dehydrated = dehydrate(queryClient);

The query key is the same [methodName, args] shape used by the nested hook leaf, so hydrated client components pick up prefetched data without a second request.

useQuery

function IssueDetail({ issueId }: { issueId: number }) {
  const { data, error, isLoading } = dyad.issues.byId.useQuery({ issueId });

  if (isLoading) return <Spinner />;
  // `error` is typed as `IssueNotFound | Forbidden` — the Python `@raises(...)` union.
  if (error) return <ErrorView kind={error.kind} />;
  return <h1>{data.title}</h1>;
}

Route leaves are always explicit. A GET /chat route is api.chat.list() / dyad.chat.list.useQuery(), not api.chat(). That keeps the same shape when /chat/messages or /chat/events are added later.

The default query key is [methodName, args]. Override it via options.queryKey when you need a custom cache key.

dyad.issues.list.useQuery(
  { status: "open" },
  {
    queryKey: ["issues", "open"],
    staleTime: 60_000,
  },
);

All other TanStack UseQueryOptions (enabled, staleTime, refetchInterval, select, …) pass through.

useMutation

function NewIssueForm() {
  const queryClient = useQueryClient();
  const createIssue = dyad.issues.create.useMutation({
    onSuccess: () =>
      queryClient.invalidateQueries({ queryKey: ["listIssues"] }),
  });

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await createIssue.mutateAsync({ data: { title: "..." } });
      }}
    >
      ...
    </form>
  );
}

mutate / mutateAsync take exactly the args the generated method expects. error is the typed error union.

useSubscription

function ActivityFeed() {
  const queryClient = useQueryClient();
  const { status, error } = dyad.activity.list.useSubscription({
    onEvent: (ev) => {
      // ev is the typed union from the Python stream return.
      if (ev.kind === "created") {
        queryClient.invalidateQueries({ queryKey: ["listIssues"] });
      }
    },
  });

  if (status === "error") return <ErrorView error={error} />;
  return <Badge status={status} />;
}

status is "idle" | "connecting" | "open" | "closed" | "error". The hook starts the stream on mount, aborts it on unmount, and re-subscribes when args change (compared by structural value). Pass enabled: false to defer.

onEvent / onOpen / onClose / onError are captured by ref, so inline arrow functions are fine — the stream is not re-established when they change identity.

How Result<T, E> is handled

Dyadpy routes decorated with @raises(...) return a Result<T, E> envelope. @dyadpy/react unwraps it automatically:

  • { ok: true, data }data is the hook's .data
  • { ok: false, error }error is thrown so TanStack Query surfaces it via .error, fully typed as the discriminated union from the Python side

Endpoints without @raises(...) (no envelope) are passed through as-is.

Peer dependencies

  • react ≥ 18
  • @tanstack/react-query ^5
  • @dyadpy/ts (workspace; required by your generated client)

License

MIT.