@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 +386 -0
- package/dist/context.d.ts +376 -0
- package/dist/context.js +344 -0
- package/dist/index.d.ts +205 -0
- package/dist/index.js +108 -0
- package/package.json +53 -0
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
|