@richie-rpc/react-query 1.0.7 → 1.0.9
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 +279 -169
- package/dist/cjs/index.cjs +228 -38
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/index.mjs +231 -38
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/types/index.d.ts +195 -57
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @richie-rpc/react-query
|
|
2
2
|
|
|
3
|
-
React hooks integration for Richie RPC using TanStack Query (React Query). Provides type-safe hooks with automatic caching, background refetching,
|
|
3
|
+
React hooks integration for Richie RPC using TanStack Query (React Query v5). Provides type-safe hooks with automatic caching, background refetching, React Suspense support, and streaming integration.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -14,48 +14,31 @@ bun add @richie-rpc/react-query @richie-rpc/client @richie-rpc/core @tanstack/re
|
|
|
14
14
|
- 🔄 **Automatic Method Detection**: GET/HEAD → queries, POST/PUT/PATCH/DELETE → mutations
|
|
15
15
|
- ⚡ **React Suspense**: Built-in support with `useSuspenseQuery`
|
|
16
16
|
- 💾 **Smart Caching**: Powered by TanStack Query
|
|
17
|
-
-
|
|
18
|
-
-
|
|
17
|
+
- 🎨 **Unified Options**: ts-rest-style `queryKey`/`queryData` pattern
|
|
18
|
+
- 📖 **Infinite Queries**: Built-in pagination support
|
|
19
|
+
- 🌊 **Streaming Integration**: TanStack Query integration via `useStreamQuery`
|
|
20
|
+
- 🔧 **Typed QueryClient**: Per-endpoint cache operations via `createTypedQueryClient`
|
|
19
21
|
|
|
20
22
|
## Quick Start
|
|
21
23
|
|
|
22
|
-
### 1.
|
|
23
|
-
|
|
24
|
-
Wrap your app with `QueryClientProvider`:
|
|
24
|
+
### 1. Create API
|
|
25
25
|
|
|
26
26
|
```tsx
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { createHooks } from '@richie-rpc/react-query';
|
|
30
|
-
import { contract } from './contract';
|
|
31
|
-
|
|
32
|
-
// Create client and hooks
|
|
33
|
-
const client = createClient(contract, {
|
|
34
|
-
baseUrl: 'http://localhost:3000',
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const hooks = createHooks(client, contract);
|
|
38
|
-
|
|
39
|
-
// Create query client
|
|
40
|
-
const queryClient = new QueryClient();
|
|
27
|
+
import { createTanstackQueryApi } from '@richie-rpc/react-query';
|
|
28
|
+
import { client, contract } from './api'; // your client setup
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<QueryClientProvider client={queryClient}>
|
|
45
|
-
<YourApp />
|
|
46
|
-
</QueryClientProvider>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
30
|
+
const api = createTanstackQueryApi(client, contract);
|
|
49
31
|
```
|
|
50
32
|
|
|
51
33
|
### 2. Use Query Hooks (GET requests)
|
|
52
34
|
|
|
53
|
-
Query hooks
|
|
35
|
+
Query hooks use the unified `queryKey`/`queryData` pattern:
|
|
54
36
|
|
|
55
37
|
```tsx
|
|
56
38
|
function UserList() {
|
|
57
|
-
const { data, isLoading, error, refetch } =
|
|
58
|
-
|
|
39
|
+
const { data, isLoading, error, refetch } = api.listUsers.useQuery({
|
|
40
|
+
queryKey: ['users', { limit: '10', offset: '0' }],
|
|
41
|
+
queryData: { query: { limit: '10', offset: '0' } },
|
|
59
42
|
});
|
|
60
43
|
|
|
61
44
|
if (isLoading) return <div>Loading...</div>;
|
|
@@ -78,9 +61,9 @@ For React Suspense integration:
|
|
|
78
61
|
|
|
79
62
|
```tsx
|
|
80
63
|
function UserListSuspense() {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
query: { limit: '10' },
|
|
64
|
+
const { data } = api.listUsers.useSuspenseQuery({
|
|
65
|
+
queryKey: ['users'],
|
|
66
|
+
queryData: { query: { limit: '10' } },
|
|
84
67
|
});
|
|
85
68
|
|
|
86
69
|
return (
|
|
@@ -104,38 +87,27 @@ function App() {
|
|
|
104
87
|
|
|
105
88
|
### 4. Use Mutation Hooks (POST/PUT/PATCH/DELETE)
|
|
106
89
|
|
|
107
|
-
Mutation hooks
|
|
90
|
+
Mutation hooks return a function to trigger the request:
|
|
108
91
|
|
|
109
92
|
```tsx
|
|
110
93
|
function CreateUserForm() {
|
|
111
|
-
const mutation =
|
|
94
|
+
const mutation = api.createUser.useMutation({
|
|
112
95
|
onSuccess: (data) => {
|
|
113
96
|
console.log('User created:', data);
|
|
114
|
-
|
|
115
|
-
queryClient.invalidateQueries({ queryKey: ['listUsers'] });
|
|
116
|
-
},
|
|
117
|
-
onError: (error) => {
|
|
118
|
-
console.error('Failed to create user:', error);
|
|
97
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
119
98
|
},
|
|
120
99
|
});
|
|
121
100
|
|
|
122
|
-
const handleSubmit = (e: FormEvent) => {
|
|
123
|
-
e.preventDefault();
|
|
124
|
-
mutation.mutate({
|
|
125
|
-
body: {
|
|
126
|
-
name: 'Alice',
|
|
127
|
-
email: 'alice@example.com',
|
|
128
|
-
age: 25,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
};
|
|
132
|
-
|
|
133
101
|
return (
|
|
134
|
-
<form
|
|
102
|
+
<form
|
|
103
|
+
onSubmit={(e) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
mutation.mutate({ body: { name: 'Alice', email: 'alice@example.com' } });
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
135
108
|
<button type="submit" disabled={mutation.isPending}>
|
|
136
109
|
{mutation.isPending ? 'Creating...' : 'Create User'}
|
|
137
110
|
</button>
|
|
138
|
-
{mutation.error && <div>Error: {mutation.error.message}</div>}
|
|
139
111
|
</form>
|
|
140
112
|
);
|
|
141
113
|
}
|
|
@@ -143,127 +115,243 @@ function CreateUserForm() {
|
|
|
143
115
|
|
|
144
116
|
## API Reference
|
|
145
117
|
|
|
146
|
-
### `
|
|
118
|
+
### `createTanstackQueryApi(client, contract)`
|
|
147
119
|
|
|
148
|
-
Creates a typed
|
|
120
|
+
Creates a typed API object from a client and contract.
|
|
149
121
|
|
|
150
122
|
**Parameters:**
|
|
151
123
|
|
|
152
124
|
- `client`: Client created with `createClient()`
|
|
153
125
|
- `contract`: Your API contract definition
|
|
154
126
|
|
|
155
|
-
**Returns:**
|
|
127
|
+
**Returns:** API object with per-endpoint hooks and methods
|
|
156
128
|
|
|
157
|
-
### Query
|
|
129
|
+
### Query Endpoint API (GET/HEAD)
|
|
158
130
|
|
|
159
|
-
#### `
|
|
131
|
+
#### `api.endpoint.useQuery(options)`
|
|
160
132
|
|
|
161
|
-
Standard query hook
|
|
133
|
+
Standard query hook.
|
|
162
134
|
|
|
163
|
-
|
|
135
|
+
```tsx
|
|
136
|
+
const { data, isLoading, error } = api.listUsers.useQuery({
|
|
137
|
+
queryKey: ['users'],
|
|
138
|
+
queryData: { query: { limit: '10' } },
|
|
139
|
+
staleTime: 5000,
|
|
140
|
+
// ...other TanStack Query options
|
|
141
|
+
});
|
|
142
|
+
```
|
|
164
143
|
|
|
165
|
-
|
|
166
|
-
- `queryOptions`: Optional TanStack Query options (staleTime, cacheTime, etc.)
|
|
144
|
+
#### `api.endpoint.useSuspenseQuery(options)`
|
|
167
145
|
|
|
168
|
-
|
|
146
|
+
Suspense-enabled query hook.
|
|
169
147
|
|
|
170
|
-
|
|
148
|
+
```tsx
|
|
149
|
+
const { data } = api.listUsers.useSuspenseQuery({
|
|
150
|
+
queryKey: ['users'],
|
|
151
|
+
queryData: { query: { limit: '10' } },
|
|
152
|
+
});
|
|
153
|
+
```
|
|
171
154
|
|
|
172
|
-
|
|
155
|
+
#### `api.endpoint.useInfiniteQuery(options)`
|
|
173
156
|
|
|
174
|
-
|
|
157
|
+
Infinite query for pagination.
|
|
175
158
|
|
|
176
|
-
|
|
177
|
-
|
|
159
|
+
```tsx
|
|
160
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = api.listUsers.useInfiniteQuery({
|
|
161
|
+
queryKey: ['users'],
|
|
162
|
+
queryData: ({ pageParam }) => ({
|
|
163
|
+
query: { limit: '10', offset: String(pageParam) },
|
|
164
|
+
}),
|
|
165
|
+
initialPageParam: 0,
|
|
166
|
+
getNextPageParam: (lastPage, allPages) => {
|
|
167
|
+
const nextOffset = allPages.length * 10;
|
|
168
|
+
return lastPage.data.users.length === 10 ? nextOffset : undefined;
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
```
|
|
178
172
|
|
|
179
|
-
|
|
173
|
+
#### `api.endpoint.useSuspenseInfiniteQuery(options)`
|
|
180
174
|
|
|
181
|
-
|
|
175
|
+
Suspense-enabled infinite query.
|
|
182
176
|
|
|
183
|
-
#### `
|
|
177
|
+
#### `api.endpoint.query(options)`
|
|
184
178
|
|
|
185
|
-
|
|
179
|
+
Direct fetch without React Query.
|
|
186
180
|
|
|
187
|
-
|
|
181
|
+
```tsx
|
|
182
|
+
const result = await api.listUsers.query({ query: { limit: '10' } });
|
|
183
|
+
```
|
|
188
184
|
|
|
189
|
-
|
|
185
|
+
### Mutation Endpoint API (POST/PUT/PATCH/DELETE)
|
|
190
186
|
|
|
191
|
-
|
|
187
|
+
#### `api.endpoint.useMutation(options?)`
|
|
192
188
|
|
|
193
|
-
|
|
189
|
+
Mutation hook.
|
|
194
190
|
|
|
195
|
-
|
|
191
|
+
```tsx
|
|
192
|
+
const mutation = api.createUser.useMutation({
|
|
193
|
+
onSuccess: (data) => console.log('Created:', data),
|
|
194
|
+
onError: (error) => console.error('Failed:', error),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
mutation.mutate({ body: { name: 'Alice', email: 'alice@example.com' } });
|
|
198
|
+
```
|
|
196
199
|
|
|
197
|
-
|
|
200
|
+
#### `api.endpoint.mutate(options)`
|
|
201
|
+
|
|
202
|
+
Direct mutate without React Query.
|
|
198
203
|
|
|
199
204
|
```tsx
|
|
200
|
-
const
|
|
201
|
-
{
|
|
202
|
-
|
|
203
|
-
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
204
|
-
cacheTime: 10 * 60 * 1000, // 10 minutes
|
|
205
|
-
refetchInterval: 30000, // Refetch every 30 seconds
|
|
206
|
-
refetchOnWindowFocus: false,
|
|
207
|
-
},
|
|
208
|
-
);
|
|
205
|
+
const result = await api.createUser.mutate({
|
|
206
|
+
body: { name: 'Alice', email: 'alice@example.com' },
|
|
207
|
+
});
|
|
209
208
|
```
|
|
210
209
|
|
|
211
|
-
###
|
|
210
|
+
### Streaming Endpoint API
|
|
212
211
|
|
|
213
|
-
|
|
212
|
+
#### `api.endpoint.stream(options)`
|
|
213
|
+
|
|
214
|
+
Direct stream access with event-based API:
|
|
214
215
|
|
|
215
216
|
```tsx
|
|
216
|
-
|
|
217
|
+
const result = await api.streamChat.stream({ body: { prompt: 'Hello' } });
|
|
217
218
|
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
result.on('chunk', (chunk) => {
|
|
220
|
+
console.log(chunk.text);
|
|
221
|
+
});
|
|
220
222
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
result.on('close', (finalResponse) => {
|
|
224
|
+
console.log('Done:', finalResponse);
|
|
225
|
+
});
|
|
226
|
+
```
|
|
225
227
|
|
|
226
|
-
|
|
227
|
-
queryClient.invalidateQueries({
|
|
228
|
-
queryKey: ['getUser', { params: { id: userId } }],
|
|
229
|
-
});
|
|
230
|
-
},
|
|
231
|
-
});
|
|
228
|
+
#### `api.endpoint.useStreamQuery(options)`
|
|
232
229
|
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
TanStack Query integration using `experimental_streamedQuery`:
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
const { data: chunks, isFetching } = api.streamChat.useStreamQuery({
|
|
234
|
+
queryKey: ['chat', prompt],
|
|
235
|
+
queryData: { body: { prompt } },
|
|
236
|
+
refetchMode: 'reset', // 'reset' | 'append' | 'replace'
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// chunks = accumulated array of chunk objects
|
|
240
|
+
// isFetching = true while streaming
|
|
235
241
|
```
|
|
236
242
|
|
|
237
|
-
###
|
|
243
|
+
### SSE Endpoint API
|
|
238
244
|
|
|
239
|
-
|
|
245
|
+
#### `api.endpoint.connect(options)`
|
|
246
|
+
|
|
247
|
+
Direct SSE connection:
|
|
240
248
|
|
|
241
249
|
```tsx
|
|
242
|
-
const
|
|
243
|
-
onMutate: async (variables) => {
|
|
244
|
-
// Cancel outgoing refetches
|
|
245
|
-
await queryClient.cancelQueries({ queryKey: ['getUser'] });
|
|
250
|
+
const connection = api.notifications.connect({ params: { id: '123' } });
|
|
246
251
|
|
|
247
|
-
|
|
248
|
-
|
|
252
|
+
connection.on('message', (data) => {
|
|
253
|
+
console.log('Message:', data.text);
|
|
254
|
+
});
|
|
249
255
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}));
|
|
256
|
+
connection.on('heartbeat', (data) => {
|
|
257
|
+
console.log('Heartbeat:', data.timestamp);
|
|
258
|
+
});
|
|
259
|
+
```
|
|
255
260
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
261
|
+
### Download Endpoint API
|
|
262
|
+
|
|
263
|
+
#### `api.endpoint.download(options)`
|
|
264
|
+
|
|
265
|
+
Direct file download:
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
const response = await api.downloadFile.download({ params: { id: 'file123' } });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### `createTypedQueryClient(queryClient, client, contract)`
|
|
272
|
+
|
|
273
|
+
Create a typed QueryClient wrapper with per-endpoint cache methods. This is useful for type-safe cache operations like prefetching, getting/setting query data, etc.
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import { createTypedQueryClient } from '@richie-rpc/react-query';
|
|
277
|
+
|
|
278
|
+
// Create at module level alongside your api
|
|
279
|
+
const typedClient = createTypedQueryClient(queryClient, client, contract);
|
|
280
|
+
|
|
281
|
+
// Type-safe cache operations
|
|
282
|
+
typedClient.listUsers.getQueryData(['users']);
|
|
283
|
+
typedClient.listUsers.setQueryData(['users'], (old) => ({
|
|
284
|
+
...old,
|
|
285
|
+
data: { ...old.data, users: [...old.data.users, newUser] },
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
// Prefetching
|
|
289
|
+
await typedClient.listUsers.prefetchQuery({
|
|
290
|
+
queryKey: ['users'],
|
|
291
|
+
queryData: { query: { limit: '10' } },
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Available methods per query endpoint:**
|
|
296
|
+
|
|
297
|
+
- `getQueryData(queryKey)` - Get cached data
|
|
298
|
+
- `setQueryData(queryKey, updater)` - Update cached data
|
|
299
|
+
- `getQueryState(queryKey)` - Get query state
|
|
300
|
+
- `fetchQuery(options)` - Fetch and cache data
|
|
301
|
+
- `prefetchQuery(options)` - Prefetch data in background
|
|
302
|
+
- `ensureQueryData(options)` - Get cached data or fetch if missing
|
|
303
|
+
|
|
304
|
+
## Error Handling
|
|
305
|
+
|
|
306
|
+
The package includes error handling utilities:
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
import { isFetchError, isUnknownErrorResponse } from '@richie-rpc/react-query';
|
|
310
|
+
|
|
311
|
+
const { error, contractEndpoint } = api.getUser.useQuery({
|
|
312
|
+
queryKey: ['user', id],
|
|
313
|
+
queryData: { params: { id } },
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (error) {
|
|
317
|
+
if (isFetchError(error)) {
|
|
318
|
+
console.log('Network error:', error.message);
|
|
319
|
+
} else if (isUnknownErrorResponse(error, contractEndpoint)) {
|
|
320
|
+
console.log('Unknown status:', error.status);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### `isFetchError(error)`
|
|
326
|
+
|
|
327
|
+
Returns `true` if the error is a network/fetch error (not a response).
|
|
328
|
+
|
|
329
|
+
### `isUnknownErrorResponse(error, endpoint)`
|
|
330
|
+
|
|
331
|
+
Returns `true` if the error is a response with a status code not defined in the contract.
|
|
332
|
+
|
|
333
|
+
### `isNotKnownResponseError(error, endpoint)`
|
|
334
|
+
|
|
335
|
+
Returns `true` if the error is either a fetch error or an unknown response error.
|
|
336
|
+
|
|
337
|
+
### `exhaustiveGuard(value)`
|
|
338
|
+
|
|
339
|
+
For compile-time exhaustiveness checking in switch statements.
|
|
340
|
+
|
|
341
|
+
## Advanced Usage
|
|
342
|
+
|
|
343
|
+
### Custom Query Options
|
|
344
|
+
|
|
345
|
+
Pass TanStack Query options alongside queryKey and queryData:
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
const { data } = api.listUsers.useQuery({
|
|
349
|
+
queryKey: ['users'],
|
|
350
|
+
queryData: { query: { limit: '10' } },
|
|
351
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
352
|
+
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
|
|
353
|
+
refetchInterval: 30000, // Refetch every 30 seconds
|
|
354
|
+
refetchOnWindowFocus: false,
|
|
267
355
|
});
|
|
268
356
|
```
|
|
269
357
|
|
|
@@ -273,78 +361,100 @@ Enable queries only when conditions are met:
|
|
|
273
361
|
|
|
274
362
|
```tsx
|
|
275
363
|
function UserPosts({ userId }: { userId: string | null }) {
|
|
276
|
-
const { data } =
|
|
277
|
-
|
|
278
|
-
{
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
// ...
|
|
364
|
+
const { data } = api.getUserPosts.useQuery({
|
|
365
|
+
queryKey: ['posts', userId],
|
|
366
|
+
queryData: { params: { userId: userId! } },
|
|
367
|
+
enabled: !!userId, // Only fetch when userId is available
|
|
368
|
+
});
|
|
284
369
|
}
|
|
285
370
|
```
|
|
286
371
|
|
|
287
|
-
###
|
|
372
|
+
### Optimistic Updates
|
|
288
373
|
|
|
289
|
-
|
|
374
|
+
Update the UI immediately before the server responds:
|
|
290
375
|
|
|
291
376
|
```tsx
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const stats = hooks.getStats.useQuery({});
|
|
295
|
-
const settings = hooks.getSettings.useQuery({});
|
|
377
|
+
// Module level - create once
|
|
378
|
+
const typedClient = createTypedQueryClient(queryClient, client, contract);
|
|
296
379
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
380
|
+
function UpdateUserForm({ userId }: { userId: string }) {
|
|
381
|
+
const mutation = api.updateUser.useMutation({
|
|
382
|
+
onMutate: async (variables) => {
|
|
383
|
+
await queryClient.cancelQueries({ queryKey: ['user', userId] });
|
|
384
|
+
const previousUser = typedClient.getUser.getQueryData(['user', userId]);
|
|
300
385
|
|
|
301
|
-
|
|
302
|
-
|
|
386
|
+
typedClient.getUser.setQueryData(['user', userId], (old) => ({
|
|
387
|
+
...old,
|
|
388
|
+
data: { ...old.data, ...variables.body },
|
|
389
|
+
}));
|
|
390
|
+
|
|
391
|
+
return { previousUser };
|
|
392
|
+
},
|
|
393
|
+
onError: (err, variables, context) => {
|
|
394
|
+
if (context?.previousUser) {
|
|
395
|
+
typedClient.getUser.setQueryData(['user', userId], context.previousUser);
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
onSettled: () => {
|
|
399
|
+
queryClient.invalidateQueries({ queryKey: ['user', userId] });
|
|
400
|
+
},
|
|
401
|
+
});
|
|
303
402
|
}
|
|
304
403
|
```
|
|
305
404
|
|
|
306
|
-
## TypeScript
|
|
405
|
+
## TypeScript Types
|
|
307
406
|
|
|
308
|
-
###
|
|
407
|
+
### Exported Types
|
|
309
408
|
|
|
310
|
-
|
|
409
|
+
```tsx
|
|
410
|
+
import type {
|
|
411
|
+
TsrQueryOptions,
|
|
412
|
+
TsrSuspenseQueryOptions,
|
|
413
|
+
TsrInfiniteQueryOptions,
|
|
414
|
+
TsrSuspenseInfiniteQueryOptions,
|
|
415
|
+
TsrMutationOptions,
|
|
416
|
+
TsrStreamQueryOptions,
|
|
417
|
+
TsrResponse,
|
|
418
|
+
TsrError,
|
|
419
|
+
TypedQueryClient,
|
|
420
|
+
TanstackQueryApi,
|
|
421
|
+
} from '@richie-rpc/react-query';
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Type Inference
|
|
425
|
+
|
|
426
|
+
Extract types from your API:
|
|
311
427
|
|
|
312
428
|
```tsx
|
|
313
429
|
import type { EndpointResponse } from '@richie-rpc/client';
|
|
314
|
-
import type { contract } from './contract';
|
|
315
430
|
|
|
316
431
|
// Get the response type for an endpoint
|
|
317
432
|
type UserListResponse = EndpointResponse<typeof contract.listUsers>;
|
|
318
|
-
|
|
319
|
-
// Or extract from hook result
|
|
320
|
-
type UserData = Awaited<ReturnType<typeof hooks.listUsers.useQuery>>['data'];
|
|
321
433
|
```
|
|
322
434
|
|
|
323
|
-
|
|
435
|
+
## TanStack Query Re-exports
|
|
324
436
|
|
|
325
|
-
|
|
437
|
+
For version consistency, you can import TanStack Query from this package:
|
|
326
438
|
|
|
327
439
|
```tsx
|
|
328
|
-
|
|
329
|
-
listUsers: (query: { limit?: string; offset?: string }) => ['listUsers', { query }] as const,
|
|
330
|
-
getUser: (id: string) => ['getUser', { params: { id } }] as const,
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
// Use in invalidation
|
|
334
|
-
queryClient.invalidateQueries({ queryKey: queryKeys.listUsers({}) });
|
|
440
|
+
import { QueryClient, QueryClientProvider } from '@richie-rpc/react-query/tanstack';
|
|
335
441
|
```
|
|
336
442
|
|
|
337
443
|
## Best Practices
|
|
338
444
|
|
|
339
|
-
1. **Create
|
|
340
|
-
2. **Use
|
|
341
|
-
3. **
|
|
342
|
-
4. **
|
|
343
|
-
5. **
|
|
445
|
+
1. **Create API once**: Create the API object at the module level, not inside components
|
|
446
|
+
2. **Use meaningful queryKeys**: Include relevant parameters in queryKey for proper cache separation
|
|
447
|
+
3. **Use Suspense for loading states**: Cleaner than manual loading state management
|
|
448
|
+
4. **Invalidate related queries**: After mutations, invalidate queries that may be affected
|
|
449
|
+
5. **Use createTypedQueryClient**: For type-safe cache operations like prefetching and setQueryData
|
|
450
|
+
6. **Handle errors exhaustively**: Use the error utilities for proper error handling
|
|
344
451
|
|
|
345
452
|
## Examples
|
|
346
453
|
|
|
347
|
-
See the `packages/demo` directory for complete working examples
|
|
454
|
+
See the `packages/demo` directory for complete working examples:
|
|
455
|
+
|
|
456
|
+
- [react-example.tsx](../demo/react-example.tsx) - Query and mutation hooks
|
|
457
|
+
- [dictionary-example.tsx](../demo/dictionary-example.tsx) - Complex data structures
|
|
348
458
|
|
|
349
459
|
## License
|
|
350
460
|
|