React

Typed TanStack Query hooks for React with @pyrpc/react.

@pyrpc/react wraps your pyRPC procedures in TanStack Query hooks. Every procedure becomes a fully typed useQuery or useMutation -- no hand-written types, no schema files.

Installation

npm install @pyrpc/react @tanstack/react-query

That's the only install you need. @pyrpc/types and @pyrpc/client ship as dependencies of every adapter, and pyrpc dev generates your types into __pyrpc.ts and wires the @pyrpc/types import to it automatically.

Project structure

my-app/
  src/
    lib/
      pyrpc.ts        <- create the client once here
    index.tsx         <- wrap root in api.Provider
    App.tsx           <- call api.greet.useQuery() etc.
  __pyrpc.ts          <- written by pyrpc dev (do not edit)

1. Create the client

src/lib/pyrpc.ts
import { createReactClient, httpBatchLink } from "@pyrpc/react"
import type { Types } from "@pyrpc/types"

export const api = createReactClient<Types>({
  links: [
    httpBatchLink({
      url: process.env.REACT_APP_API_URL ?? "http://localhost:8000",
    }),
  ],
})

api is a single object. It carries every procedure as a hook, plus api.Provider for the TanStack Query cache.

2. Add the Provider

Wrap your root component once -- this sets up the shared TanStack Query cache:

src/index.tsx
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
import { api } from "./lib/pyrpc"

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <api.Provider>
      <App />
    </api.Provider>
  </React.StrictMode>
)

3. Call procedures

src/App.tsx
import { useState } from "react"
import { api } from "./lib/pyrpc"

export function App() {
  const [name, setName] = useState("")

  // @rpc.query maps to useQuery
  const { data: greeting, isLoading } = api.greet.useQuery({ name: "World" })

  // @rpc.mutation maps to useMutation
  const createItem = api.create_item.useMutation()

  return (
    <div>
      {isLoading ? <p>Loading</p> : <pre>{JSON.stringify(greeting)}</pre>}

      <input value={name} onChange={e => setName(e.target.value)} />
      <button
        onClick={() => createItem.mutate({ name })}
        disabled={createItem.isPending}
      >
        {createItem.isPending ? "Creating" : "Create"}
      </button>

      {createItem.isSuccess && <pre>{JSON.stringify(createItem.data)}</pre>}
    </div>
  )
}

useQuery and useMutation are the standard TanStack Query hooks. All their options (enabled, staleTime, onSuccess, etc.) work exactly as documented in the TanStack Query docs.

Invalidating after a mutation

const utils = api.useUtils()
const createItem = api.create_item.useMutation({
  onSuccess: () => {
    utils.list_items.invalidate()
  },
})

TypeScript

All parameter types, return types, and error shapes are inferred from your Python function signatures. There is nothing extra to annotate.

// Python: @rpc.query \n def greet(name: str) -> dict: ...
api.greet.useQuery({ name: "Ada" })  // correct
api.greet.useQuery({ nme: "Ada" })   // type error -- typo caught at compile time

Client config

createReactClient accepts the same config as the vanilla client:

OptionTypeDefaultDescription
linksLink[]requiredLink pipeline; exactly one terminating link (httpLink or httpBatchLink)
kindsProcedureKindMapgeneratedOverride generated procedure kinds

Full working examples