Every RPC framework eventually faces the same question: how does the frontend handle server state? You can hand-roll fetch + useState. Or you can use a library built for exactly this job. We chose TanStack Query (formerly React Query) because it solves the hard problems we did not want to reinvent.
What TanStack Query is
TanStack Query is a server-state management library. It handles caching, background refetching, stale-while-revalidate, optimistic updates, and request deduplication. It works with React, Vue, Svelte, and Angular. It is not an RPC library — it is the layer that makes RPC calls feel instant.
The five problems it solves
1. Caching. Without a cache, every component that needs user data makes its own HTTP request. With TanStack Query, the first component fetches, and every other component reads from the in-memory cache. One network call, many consumers.
2. Stale-while-revalidate. Show cached data immediately, refetch in the background, update the UI when the fresh data arrives. No loading spinners on every navigation.
3. Background refetching. When the user switches tabs and comes back, the data might be stale. TanStack Query refetches automatically when the window regains focus. Your RPC calls stay fresh without manual polling.
4. Deduplication. If three components mount simultaneously and all call get_user(1), TanStack Query sends one network request, not three.
5. Mutation invalidation. After a mutation (like update_user), you invalidate the query cache for that user. The UI refetches automatically. No manual setState dance.
Why not build our own
tRPC bundles its own query layer on top of React Query. We considered doing the same but decided against it. TanStack Query is already the standard. It has 45k+ GitHub stars, a massive ecosystem, and framework adapters for React, Vue, Svelte, and Solid. Building our own would mean maintaining cache logic, devtools integration, and persistence adapters — none of which are pyRPC's core value.
What pyRPC adds on top
TanStack Query does not know about RPC. It needs a queryFn — a function that returns a Promise. pyRPC's adapters generate that function from your procedure definitions:
// TanStack Query needs this:
useQuery({
queryKey: ["pyrpc", "get_user", { userId: 1 }],
queryFn: () => fetch("/rpc", { method: "POST", body: ... }),
})
// pyRPC generates it from your procedure name:
api.get_user.useQuery({ userId: 1 })The adapter builds the query key, wraps the RPC call in a queryFn, and infers the return type. You write one line instead of ten.
Query keys are predictable
Every pyRPC query key is ["pyrpc", procedureName, input?]. That means you can invalidate precisely:
const utils = api.useUtils()
await utils.invalidate("pyrpc", "get_user")Framework coverage
TanStack Query has official adapters for React, Vue, Svelte, Angular, and Solid. pyRPC ships adapters for the four most common choices. If you use a framework we do not cover, @pyrpc/client works directly with fetch — no adapter needed.
The cost
TanStack Query adds ~13kB gzipped to your bundle. For the caching, refetching, and devtools you get in return, that is a trade-off almost every production app makes. If you are building something tiny, the vanilla createClient from @pyrpc/client is always available.

pyRPC