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