0
# Helper Functions
1
2
Type-safe helper functions for creating query and infinite query options, providing enhanced TypeScript inference and reusable query configurations.
3
4
## Capabilities
5
6
### queryOptions
7
8
Helper function for creating type-safe query options with enhanced TypeScript inference.
9
10
```typescript { .api }
11
/**
12
* Create type-safe query options with enhanced TypeScript inference
13
* @param options - Query options with defined initial data
14
* @returns Enhanced query options with type information
15
*/
16
function queryOptions<TQueryFnData, TError, TData, TQueryKey>(
17
options: DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>
18
): DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
19
queryKey: DataTag<TQueryKey, TQueryFnData, TError>;
20
};
21
22
/**
23
* Create type-safe query options with enhanced TypeScript inference
24
* @param options - Query options with undefined initial data
25
* @returns Enhanced query options with type information
26
*/
27
function queryOptions<TQueryFnData, TError, TData, TQueryKey>(
28
options: UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>
29
): UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
30
queryKey: DataTag<TQueryKey, TQueryFnData, TError>;
31
};
32
33
// Base implementation for runtime usage
34
function queryOptions(options: unknown): unknown;
35
```
36
37
**Usage Examples:**
38
39
```typescript
40
import { queryOptions, useQuery } from '@tanstack/vue-query';
41
42
// Basic query options
43
const todosQuery = queryOptions({
44
queryKey: ['todos'],
45
queryFn: () => fetch('/api/todos').then(res => res.json()),
46
staleTime: 1000 * 60 * 5, // 5 minutes
47
});
48
49
// Use with useQuery - full type inference
50
const { data: todos } = useQuery(todosQuery);
51
52
// Query options with parameters
53
const userQuery = (userId: number) => queryOptions({
54
queryKey: ['user', userId],
55
queryFn: ({ queryKey }) =>
56
fetch(`/api/users/${queryKey[1]}`).then(res => res.json()),
57
enabled: !!userId,
58
});
59
60
// Use with dynamic parameters
61
const userId = ref(1);
62
const { data: user } = useQuery(userQuery(userId.value));
63
64
// Query options with data transformation
65
const postsQuery = queryOptions({
66
queryKey: ['posts'],
67
queryFn: () => fetch('/api/posts').then(res => res.json()),
68
select: (posts) => posts.filter(post => post.published),
69
staleTime: 1000 * 60 * 10,
70
});
71
72
// Query options with initial data
73
const cachedUserQuery = (userId: number, initialData?: User) => queryOptions({
74
queryKey: ['user', userId],
75
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
76
initialData,
77
staleTime: initialData ? 0 : 1000 * 60 * 5,
78
});
79
80
// Reusable query configurations
81
const createApiQuery = <T>(endpoint: string) => queryOptions({
82
queryKey: ['api', endpoint],
83
queryFn: () => fetch(`/api/${endpoint}`).then(res => res.json()) as Promise<T>,
84
retry: 3,
85
staleTime: 1000 * 60 * 5,
86
});
87
88
// Use reusable configuration
89
const usersQuery = createApiQuery<User[]>('users');
90
const productsQuery = createApiQuery<Product[]>('products');
91
92
// Complex query with multiple options
93
const dashboardQuery = queryOptions({
94
queryKey: ['dashboard', 'summary'],
95
queryFn: async () => {
96
const [users, posts, analytics] = await Promise.all([
97
fetch('/api/users/count').then(r => r.json()),
98
fetch('/api/posts/recent').then(r => r.json()),
99
fetch('/api/analytics/summary').then(r => r.json()),
100
]);
101
return { users, posts, analytics };
102
},
103
select: (data) => ({
104
userCount: data.users.total,
105
recentPosts: data.posts.slice(0, 5),
106
pageViews: data.analytics.pageViews,
107
conversionRate: data.analytics.conversions / data.analytics.visits,
108
}),
109
staleTime: 1000 * 60 * 10,
110
refetchInterval: 1000 * 30, // Refresh every 30 seconds
111
meta: { critical: true },
112
});
113
114
// Conditional query options
115
const conditionalQuery = (enabled: boolean) => queryOptions({
116
queryKey: ['conditional-data'],
117
queryFn: () => fetch('/api/data').then(res => res.json()),
118
enabled,
119
retry: enabled ? 3 : 0,
120
});
121
```
122
123
### infiniteQueryOptions
124
125
Helper function for creating type-safe infinite query options with enhanced TypeScript inference.
126
127
```typescript { .api }
128
/**
129
* Create type-safe infinite query options with enhanced TypeScript inference
130
* @param options - Infinite query options with undefined initial data
131
* @returns Enhanced infinite query options with type information
132
*/
133
function infiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>(
134
options: UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
135
): UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam> & {
136
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>;
137
};
138
139
/**
140
* Create type-safe infinite query options with enhanced TypeScript inference
141
* @param options - Infinite query options with defined initial data
142
* @returns Enhanced infinite query options with type information
143
*/
144
function infiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>(
145
options: DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
146
): DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam> & {
147
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>;
148
};
149
150
// Base implementation for runtime usage
151
function infiniteQueryOptions(options: unknown): unknown;
152
```
153
154
**Usage Examples:**
155
156
```typescript
157
import { infiniteQueryOptions, useInfiniteQuery } from '@tanstack/vue-query';
158
159
// Basic infinite query options
160
const postsInfiniteQuery = infiniteQueryOptions({
161
queryKey: ['posts', 'infinite'],
162
queryFn: ({ pageParam = 1 }) =>
163
fetch(`/api/posts?page=${pageParam}&limit=10`).then(res => res.json()),
164
initialPageParam: 1,
165
getNextPageParam: (lastPage, allPages) =>
166
lastPage.hasMore ? allPages.length + 1 : undefined,
167
staleTime: 1000 * 60 * 5,
168
});
169
170
// Use with useInfiniteQuery
171
const {
172
data: posts,
173
fetchNextPage,
174
hasNextPage,
175
isFetchingNextPage
176
} = useInfiniteQuery(postsInfiniteQuery);
177
178
// Infinite query with search parameters
179
const searchInfiniteQuery = (searchTerm: string) => infiniteQueryOptions({
180
queryKey: ['search', searchTerm, 'infinite'],
181
queryFn: ({ queryKey, pageParam = 0 }) =>
182
fetch(`/api/search?q=${queryKey[1]}&offset=${pageParam}&limit=20`)
183
.then(res => res.json()),
184
initialPageParam: 0,
185
getNextPageParam: (lastPage, allPages, lastPageParam) =>
186
lastPage.results.length === 20 ? lastPageParam + 20 : undefined,
187
getPreviousPageParam: (firstPage, allPages, firstPageParam) =>
188
firstPageParam > 0 ? Math.max(0, firstPageParam - 20) : undefined,
189
enabled: !!searchTerm && searchTerm.length > 2,
190
staleTime: 1000 * 60 * 2,
191
});
192
193
// Use with reactive search term
194
const searchTerm = ref('');
195
const searchResults = useInfiniteQuery(
196
computed(() => searchInfiniteQuery(searchTerm.value))
197
);
198
199
// Infinite query with cursor-based pagination
200
const cursorInfiniteQuery = infiniteQueryOptions({
201
queryKey: ['timeline'],
202
queryFn: ({ pageParam }) =>
203
fetch(`/api/timeline${pageParam ? `?cursor=${pageParam}` : ''}`)
204
.then(res => res.json()),
205
initialPageParam: undefined,
206
getNextPageParam: (lastPage) => lastPage.nextCursor,
207
getPreviousPageParam: (firstPage) => firstPage.prevCursor,
208
select: (data) => ({
209
pages: data.pages,
210
pageParams: data.pageParams,
211
items: data.pages.flatMap(page => page.items),
212
}),
213
});
214
215
// Complex infinite query with filtering
216
const filteredInfiniteQuery = (category: string, sortBy: string) => infiniteQueryOptions({
217
queryKey: ['products', 'infinite', category, sortBy],
218
queryFn: ({ queryKey, pageParam = 1 }) => {
219
const [, , category, sortBy] = queryKey;
220
return fetch(
221
`/api/products?category=${category}&sort=${sortBy}&page=${pageParam}`
222
).then(res => res.json());
223
},
224
initialPageParam: 1,
225
getNextPageParam: (lastPage, allPages) => {
226
if (lastPage.products.length < lastPage.pageSize) return undefined;
227
return allPages.length + 1;
228
},
229
select: (data) => ({
230
...data,
231
totalProducts: data.pages.reduce((sum, page) => sum + page.products.length, 0),
232
categories: [...new Set(data.pages.flatMap(page =>
233
page.products.map(p => p.category)
234
))],
235
}),
236
staleTime: 1000 * 60 * 5,
237
maxPages: 10, // Limit to prevent excessive memory usage
238
});
239
240
// Infinite query with optimistic updates
241
const commentsInfiniteQuery = (postId: number) => infiniteQueryOptions({
242
queryKey: ['post', postId, 'comments'],
243
queryFn: ({ pageParam = 1 }) =>
244
fetch(`/api/posts/${postId}/comments?page=${pageParam}`)
245
.then(res => res.json()),
246
initialPageParam: 1,
247
getNextPageParam: (lastPage, allPages) =>
248
lastPage.hasMore ? allPages.length + 1 : undefined,
249
select: (data) => ({
250
...data,
251
allComments: data.pages.flatMap(page => page.comments),
252
totalCount: data.pages[0]?.totalCount || 0,
253
}),
254
});
255
256
// Reusable infinite query factory
257
const createInfiniteListQuery = <T>(
258
endpoint: string,
259
pageSize: number = 20
260
) => infiniteQueryOptions({
261
queryKey: [endpoint, 'infinite'],
262
queryFn: ({ pageParam = 0 }) =>
263
fetch(`/api/${endpoint}?offset=${pageParam}&limit=${pageSize}`)
264
.then(res => res.json()) as Promise<{
265
items: T[];
266
hasMore: boolean;
267
total: number;
268
}>,
269
initialPageParam: 0,
270
getNextPageParam: (lastPage, allPages) =>
271
lastPage.hasMore ? allPages.length * pageSize : undefined,
272
staleTime: 1000 * 60 * 5,
273
});
274
275
// Use reusable factory
276
const usersInfiniteQuery = createInfiniteListQuery<User>('users');
277
const ordersInfiniteQuery = createInfiniteListQuery<Order>('orders', 50);
278
```
279
280
## Advanced Patterns
281
282
**Usage Examples:**
283
284
```typescript
285
// Query options with derived data
286
const createDerivedQuery = <TData, TDerived>(
287
baseOptions: Parameters<typeof queryOptions>[0],
288
derive: (data: TData) => TDerived
289
) => queryOptions({
290
...baseOptions,
291
select: (data: TData) => derive(data),
292
});
293
294
const statsQuery = createDerivedQuery(
295
{
296
queryKey: ['raw-stats'],
297
queryFn: () => fetch('/api/stats').then(res => res.json()),
298
},
299
(rawStats) => ({
300
summary: {
301
total: rawStats.reduce((sum, stat) => sum + stat.value, 0),
302
average: rawStats.length > 0 ? rawStats.reduce((sum, stat) => sum + stat.value, 0) / rawStats.length : 0,
303
max: Math.max(...rawStats.map(stat => stat.value)),
304
min: Math.min(...rawStats.map(stat => stat.value)),
305
},
306
byCategory: rawStats.reduce((acc, stat) => {
307
acc[stat.category] = (acc[stat.category] || 0) + stat.value;
308
return acc;
309
}, {}),
310
})
311
);
312
313
// Query options with error boundary
314
const createSafeQuery = <T>(
315
options: Parameters<typeof queryOptions>[0],
316
fallbackData: T
317
) => queryOptions({
318
...options,
319
select: (data) => data || fallbackData,
320
retry: (failureCount, error) => {
321
if (error.status === 404) return false;
322
return failureCount < 3;
323
},
324
throwOnError: false,
325
});
326
327
const safeUserQuery = (userId: number) => createSafeQuery(
328
{
329
queryKey: ['user', userId],
330
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
331
},
332
{ id: userId, name: 'Unknown User', email: '' }
333
);
334
335
// Prefetch helper using query options
336
const usePrefetch = () => {
337
const queryClient = useQueryClient();
338
339
const prefetchQuery = async (options: ReturnType<typeof queryOptions>) => {
340
await queryClient.prefetchQuery(options);
341
};
342
343
const prefetchInfiniteQuery = async (
344
options: ReturnType<typeof infiniteQueryOptions>
345
) => {
346
await queryClient.prefetchInfiniteQuery(options);
347
};
348
349
return { prefetchQuery, prefetchInfiniteQuery };
350
};
351
352
// Usage with prefetch
353
const { prefetchQuery } = usePrefetch();
354
355
const handleUserHover = (userId: number) => {
356
prefetchQuery(userQuery(userId));
357
};
358
```
359
360
## Types
361
362
```typescript { .api }
363
// Type enhancement for query options
364
type DataTag<TQueryKey, TQueryFnData, TError> = TQueryKey & {
365
_dataType?: TQueryFnData;
366
_errorType?: TError;
367
};
368
369
// Query options variants
370
interface DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>
371
extends UseQueryOptions<TQueryFnData, TError, TData, TQueryKey> {
372
initialData: TQueryFnData | (() => TQueryFnData);
373
}
374
375
interface UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>
376
extends UseQueryOptions<TQueryFnData, TError, TData, TQueryKey> {
377
initialData?: undefined | (() => undefined);
378
}
379
380
// Infinite query options variants
381
interface DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
382
extends UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam> {
383
initialData:
384
| InfiniteData<TQueryFnData, TPageParam>
385
| (() => InfiniteData<TQueryFnData, TPageParam>);
386
}
387
388
interface UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
389
extends UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam> {
390
initialData?: undefined;
391
}
392
393
// Data structure for infinite queries
394
interface InfiniteData<TData, TPageParam = unknown> {
395
pages: TData[];
396
pageParams: TPageParam[];
397
}
398
399
// Base query key type
400
type QueryKey = ReadonlyArray<unknown>;
401
402
// Error type
403
type DefaultError = Error;
404
```