@srpc.org/react-query 0.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,386 @@
1
+ # @srpc/react-query
2
+
3
+ React Query integration for SRPC - Type-safe RPC with automatic React Query hooks and query/mutation options generation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Using JSR (recommended)
9
+ deno add @srpc/react-query
10
+ npx jsr add @srpc/react-query
11
+ yarn dlx jsr add @srpc/react-query
12
+ pnpm dlx jsr add @srpc/react-query
13
+ bunx jsr add @srpc/react-query
14
+ ```
15
+
16
+ ## Features
17
+
18
+ - **Automatic React Query Integration**: Transform SRPC procedures into React Query options
19
+ - **Type-safe Hooks**: Full TypeScript support with automatic type inference
20
+ - **React Context Support**: Easy setup with Context API for app-wide RPC access
21
+ - **Query & Mutation Options**: Automatic generation of `queryOptions` and `mutationOptions`
22
+ - **Nested Router Support**: Works seamlessly with nested SRPC routers
23
+ - **Zero Configuration**: Works out of the box with TanStack React Query v5+
24
+
25
+ ## Prerequisites
26
+
27
+ This package requires:
28
+ - `@srpc/core` - The core SRPC framework
29
+ - `@tanstack/react-query` v5.90 or higher
30
+ - `react` v19 or higher
31
+
32
+ ## Quick Start
33
+
34
+ ### 1. Define Your Server Router
35
+
36
+ ```typescript
37
+ // server/router.ts
38
+ import { initSRPC } from "@srpc/core/server";
39
+
40
+ const s = initSRPC();
41
+
42
+ export const appRouter = s.router({
43
+ sayHello: async (_, name: string) => {
44
+ return `Hello ${name}!`;
45
+ },
46
+ users: s.router({
47
+ getUser: async (_, id: number) => {
48
+ return { id, name: "John Doe", email: "john@example.com" };
49
+ },
50
+ createUser: async (_, data: { name: string; email: string }) => {
51
+ return { id: 1, ...data };
52
+ },
53
+ }),
54
+ });
55
+
56
+ export type AppRouter = typeof appRouter;
57
+ ```
58
+
59
+ ### 2. Create SRPC Client and Context
60
+
61
+ ```typescript
62
+ // lib/rpc.ts
63
+ import { createSRPCClient } from "@srpc/core/client";
64
+ import { createSRPCContext } from "@srpc.org/react-query";
65
+ import type { AppRouter } from "../server/router";
66
+
67
+ // Create SRPC client
68
+ export const rpcClient = createSRPCClient<AppRouter>({
69
+ endpoint: "/api/srpc",
70
+ });
71
+
72
+ // Create React context, provider, and hooks
73
+ export const { SRPCProvider, useSRPC, useSRPCClient } =
74
+ createSRPCContext<AppRouter>();
75
+ ```
76
+
77
+ ### 3. Setup Providers
78
+
79
+ ```typescript
80
+ // app/providers.tsx
81
+ "use client";
82
+
83
+ import { rpcClient, SRPCProvider } from "@/lib/rpc";
84
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
85
+ import { useState } from "react";
86
+
87
+ export function Providers({ children }: { children: React.ReactNode }) {
88
+ const [queryClient] = useState(() => new QueryClient());
89
+
90
+ return (
91
+ <QueryClientProvider client={queryClient}>
92
+ <SRPCProvider client={rpcClient}>
93
+ {children}
94
+ </SRPCProvider>
95
+ </QueryClientProvider>
96
+ );
97
+ }
98
+ ```
99
+
100
+ ### 4. Use in Components
101
+
102
+ ```typescript
103
+ // components/UserProfile.tsx
104
+ "use client";
105
+
106
+ import { useSRPC } from "@/lib/rpc";
107
+ import { useQuery } from "@tanstack/react-query";
108
+
109
+ export function UserProfile({ userId }: { userId: number }) {
110
+ const srpc = useSRPC();
111
+
112
+ // Get React Query options for the procedure
113
+ const userQuery = useQuery(srpc.users.getUser.queryOptions(userId));
114
+
115
+ if (userQuery.isLoading) return <div>Loading...</div>;
116
+ if (userQuery.error) return <div>Error: {userQuery.error.message}</div>;
117
+
118
+ return (
119
+ <div>
120
+ <h2>{userQuery.data.name}</h2>
121
+ <p>{userQuery.data.email}</p>
122
+ </div>
123
+ );
124
+ }
125
+ ```
126
+
127
+ ## Usage Patterns
128
+
129
+ ### Using Queries
130
+
131
+ ```typescript
132
+ import { useSRPC } from "@/lib/rpc";
133
+ import { useQuery } from "@tanstack/react-query";
134
+
135
+ function MyComponent() {
136
+ const srpc = useSRPC();
137
+
138
+ // Basic query
139
+ const { data, isLoading, error } = useQuery(
140
+ srpc.users.getUser.queryOptions(1)
141
+ );
142
+
143
+ // Query with options
144
+ const userQuery = useQuery({
145
+ ...srpc.users.getUser.queryOptions(userId),
146
+ staleTime: 5000,
147
+ refetchInterval: 10000,
148
+ });
149
+
150
+ return <div>{data?.name}</div>;
151
+ }
152
+ ```
153
+
154
+ ### Using Mutations
155
+
156
+ ```typescript
157
+ import { useSRPC } from "@/lib/rpc";
158
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
159
+
160
+ function CreateUserForm() {
161
+ const srpc = useSRPC();
162
+ const queryClient = useQueryClient();
163
+
164
+ const createUser = useMutation({
165
+ ...srpc.users.createUser.mutationOptions(),
166
+ onSuccess: () => {
167
+ // Invalidate and refetch
168
+ queryClient.invalidateQueries({ queryKey: ["users"] });
169
+ },
170
+ });
171
+
172
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
173
+ e.preventDefault();
174
+ const formData = new FormData(e.currentTarget);
175
+ createUser.mutate({
176
+ name: formData.get("name") as string,
177
+ email: formData.get("email") as string,
178
+ });
179
+ };
180
+
181
+ return (
182
+ <form onSubmit={handleSubmit}>
183
+ <input name="name" placeholder="Name" />
184
+ <input name="email" type="email" placeholder="Email" />
185
+ <button type="submit" disabled={createUser.isPending}>
186
+ {createUser.isPending ? "Creating..." : "Create User"}
187
+ </button>
188
+ {createUser.error && <p>Error: {createUser.error.message}</p>}
189
+ </form>
190
+ );
191
+ }
192
+ ```
193
+
194
+ ### Direct Client Access
195
+
196
+ If you need to call procedures outside of React Query:
197
+
198
+ ```typescript
199
+ import { useSRPCClient } from "@/lib/rpc";
200
+
201
+ function MyComponent() {
202
+ const client = useSRPCClient();
203
+
204
+ const handleClick = async () => {
205
+ // Direct RPC call without React Query
206
+ const result = await client.sayHello("World");
207
+ console.log(result); // "Hello World!"
208
+ };
209
+
210
+ return <button onClick={handleClick}>Say Hello</button>;
211
+ }
212
+ ```
213
+
214
+ ### Nested Routers
215
+
216
+ The integration works seamlessly with nested routers:
217
+
218
+ ```typescript
219
+ const srpc = useSRPC();
220
+
221
+ // Access nested procedures
222
+ const userQuery = useQuery(srpc.users.getUser.queryOptions(1));
223
+ const adminQuery = useQuery(srpc.users.admin.getStats.queryOptions());
224
+ const postQuery = useQuery(srpc.posts.drafts.list.queryOptions());
225
+ ```
226
+
227
+ ### Using Without React Context
228
+
229
+ If you prefer not to use React Context:
230
+
231
+ ```typescript
232
+ import { createSRPCClient } from "@srpc/core/client";
233
+ import { createSRPCQueryOptions } from "@srpc.org/react-query";
234
+ import { useQuery } from "@tanstack/react-query";
235
+ import type { AppRouter } from "./server/router";
236
+
237
+ // Create client
238
+ const client = createSRPCClient<AppRouter>({
239
+ endpoint: "/api/srpc",
240
+ });
241
+
242
+ // Create query options
243
+ const srpc = createSRPCQueryOptions({ client });
244
+
245
+ // Use directly in components
246
+ function MyComponent() {
247
+ const { data } = useQuery(srpc.users.getUser.queryOptions(1));
248
+ return <div>{data?.name}</div>;
249
+ }
250
+ ```
251
+
252
+ ## Advanced Usage
253
+
254
+ ### Custom Serialization
255
+
256
+ ```typescript
257
+ import { createSRPCClient } from "@srpc/core/client";
258
+ import { createSRPCContext } from "@srpc.org/react-query";
259
+ import superjson from "superjson";
260
+
261
+ const client = createSRPCClient<AppRouter>({
262
+ endpoint: "/api/srpc",
263
+ transformer: {
264
+ serialize: (value) => superjson.stringify(value),
265
+ deserialize: (value) => superjson.parse(value),
266
+ },
267
+ });
268
+
269
+ export const { SRPCProvider, useSRPC } = createSRPCContext<AppRouter>();
270
+ ```
271
+
272
+ ### Authentication Headers
273
+
274
+ ```typescript
275
+ const client = createSRPCClient<AppRouter>({
276
+ endpoint: "/api/srpc",
277
+ headers: async () => {
278
+ const token = await getAuthToken();
279
+ return {
280
+ Authorization: `Bearer ${token}`,
281
+ };
282
+ },
283
+ });
284
+ ```
285
+
286
+ ### Optimistic Updates
287
+
288
+ ```typescript
289
+ const updateUser = useMutation({
290
+ ...srpc.users.updateUser.mutationOptions(),
291
+ onMutate: async (newUser) => {
292
+ // Cancel outgoing refetches
293
+ await queryClient.cancelQueries({ queryKey: ["users", newUser.id] });
294
+
295
+ // Snapshot previous value
296
+ const previous = queryClient.getQueryData(["users", newUser.id]);
297
+
298
+ // Optimistically update
299
+ queryClient.setQueryData(["users", newUser.id], newUser);
300
+
301
+ return { previous };
302
+ },
303
+ onError: (err, newUser, context) => {
304
+ // Rollback on error
305
+ queryClient.setQueryData(["users", newUser.id], context?.previous);
306
+ },
307
+ onSettled: (data, error, variables) => {
308
+ // Refetch after success or error
309
+ queryClient.invalidateQueries({ queryKey: ["users", variables.id] });
310
+ },
311
+ });
312
+ ```
313
+
314
+ ## API Reference
315
+
316
+ ### createSRPCContext<TRouter>()
317
+
318
+ Creates a React context and related hooks for SRPC with React Query integration.
319
+
320
+ **Returns:**
321
+ - `SRPCContext` - React Context object
322
+ - `SRPCProvider` - Provider component that accepts `client` prop
323
+ - `useSRPC()` - Hook that returns decorated procedures with `.queryOptions()` and `.mutationOptions()`
324
+ - `useSRPCClient()` - Hook that returns the raw SRPC client for direct calls
325
+
326
+ ### createSRPCQueryOptions({ client })
327
+
328
+ Transforms an SRPC client into an object with React Query options accessors.
329
+
330
+ **Parameters:**
331
+ - `client` - DecoratedProcedureRecord from `createSRPCClient`
332
+
333
+ **Returns:**
334
+ - Decorated procedures where each procedure has:
335
+ - `.queryOptions(...args)` - Returns `UseQueryOptions` for `useQuery`
336
+ - `.mutationOptions()` - Returns `UseMutationOptions` for `useMutation`
337
+
338
+ ### useSRPC()
339
+
340
+ Hook to access decorated SRPC procedures with React Query options.
341
+
342
+ **Must be used within `<SRPCProvider>`**
343
+
344
+ **Returns:**
345
+ - Decorated procedures with `.queryOptions()` and `.mutationOptions()` methods
346
+
347
+ ### useSRPCClient()
348
+
349
+ Hook to access the raw SRPC client for direct procedure calls.
350
+
351
+ **Must be used within `<SRPCProvider>`**
352
+
353
+ **Returns:**
354
+ - Raw SRPC client with direct procedure methods
355
+
356
+ ## TypeScript Support
357
+
358
+ All functions and hooks are fully typed with automatic type inference:
359
+
360
+ ```typescript
361
+ // Types are automatically inferred from your router
362
+ const srpc = useSRPC();
363
+
364
+ // TypeScript knows the input types
365
+ const query = srpc.users.getUser.queryOptions(1); // ✓ number expected
366
+
367
+ // And the output types
368
+ const { data } = useQuery(query);
369
+ // data is typed as { id: number; name: string; email: string }
370
+
371
+ // Type errors are caught at compile time
372
+ srpc.users.getUser.queryOptions("invalid"); // ✗ Type error
373
+ ```
374
+
375
+ ## Examples
376
+
377
+ See the [web app example](../../apps/web) for a complete working implementation with:
378
+ - Server setup with SRPC
379
+ - Client configuration
380
+ - Provider setup
381
+ - Component usage patterns
382
+ - Server Components integration
383
+
384
+ ## License
385
+
386
+ MIT