0
# Query Hooks
1
2
React hooks for data fetching with caching, background updates, and full type safety. These hooks are automatically generated for each query procedure in your tRPC router.
3
4
## Capabilities
5
6
### useQuery
7
8
Primary hook for data fetching with caching and background synchronization.
9
10
```typescript { .api }
11
/**
12
* Hook for fetching data from a tRPC query procedure
13
* @param input - Input parameters for the procedure
14
* @param opts - Query configuration options
15
* @returns Query result with data, loading states, and error information
16
*/
17
procedure.useQuery<TQueryFnData = TOutput, TData = TQueryFnData>(
18
input: TInput | SkipToken,
19
opts?: UseTRPCQueryOptions<TQueryFnData, TData, TError, TOutput>
20
): UseTRPCQueryResult<TData, TError>;
21
22
// Overload for defined initial data
23
procedure.useQuery<TQueryFnData = TOutput, TData = TQueryFnData>(
24
input: TInput | SkipToken,
25
opts: DefinedUseTRPCQueryOptions<TQueryFnData, TData, TError, TOutput>
26
): DefinedUseTRPCQueryResult<TData, TError>;
27
28
interface UseTRPCQueryOptions<TOutput, TData, TError, TQueryOptsData = TOutput>
29
extends Omit<UseBaseQueryOptions<TOutput, TError, TData, TQueryOptsData>, 'queryKey'> {
30
trpc?: TRPCReactRequestOptions;
31
}
32
33
interface UseTRPCQueryResult<TData, TError> extends UseQueryResult<TData, TError> {
34
trpc: TRPCHookResult;
35
}
36
37
interface TRPCHookResult {
38
path: string[];
39
}
40
```
41
42
**Usage Examples:**
43
44
```typescript
45
import { trpc } from "./utils/trpc";
46
47
function UserProfile({ userId }: { userId: number }) {
48
// Basic query
49
const { data, error, isLoading } = trpc.user.get.useQuery({ id: userId });
50
51
// With options
52
const { data: user } = trpc.user.get.useQuery(
53
{ id: userId },
54
{
55
enabled: !!userId,
56
staleTime: 5 * 60 * 1000, // 5 minutes
57
refetchOnWindowFocus: false,
58
}
59
);
60
61
// With data transformation
62
const { data: userName } = trpc.user.get.useQuery(
63
{ id: userId },
64
{
65
select: (user) => user.name,
66
}
67
);
68
69
// Skip query conditionally
70
const { data } = trpc.user.get.useQuery(
71
userId ? { id: userId } : skipToken
72
);
73
74
if (error) return <div>Error: {error.message}</div>;
75
if (isLoading) return <div>Loading...</div>;
76
77
return <div>Hello, {data?.name}!</div>;
78
}
79
```
80
81
### useSuspenseQuery
82
83
Suspense-enabled query hook that automatically suspends the component during loading.
84
85
```typescript { .api }
86
/**
87
* Suspense-enabled hook for fetching data from a tRPC query procedure
88
* @param input - Input parameters for the procedure
89
* @param opts - Suspense query configuration options
90
* @returns Tuple with data and query result (data is always defined)
91
*/
92
procedure.useSuspenseQuery<TQueryFnData = TOutput, TData = TQueryFnData>(
93
input: TInput,
94
opts?: UseTRPCSuspenseQueryOptions<TQueryFnData, TData, TError>
95
): [TData, UseSuspenseQueryResult<TData, TError> & TRPCHookResult];
96
97
interface UseTRPCSuspenseQueryOptions<TOutput, TData, TError>
98
extends Omit<UseSuspenseQueryOptions<TOutput, TError, TData>, 'queryKey'> {
99
trpc?: TRPCReactRequestOptions;
100
}
101
```
102
103
**Usage Examples:**
104
105
```typescript
106
import { Suspense } from "react";
107
108
function UserProfileSuspense({ userId }: { userId: number }) {
109
// Suspense query - data is always defined
110
const [user, query] = trpc.user.get.useSuspenseQuery({ id: userId });
111
112
return (
113
<div>
114
<h1>{user.name}</h1>
115
<p>Email: {user.email}</p>
116
{query.isFetching && <span>Updating...</span>}
117
</div>
118
);
119
}
120
121
function App() {
122
return (
123
<Suspense fallback={<div>Loading user...</div>}>
124
<UserProfileSuspense userId={1} />
125
</Suspense>
126
);
127
}
128
```
129
130
### useInfiniteQuery
131
132
Hook for paginated/infinite data fetching with cursor-based pagination support.
133
134
```typescript { .api }
135
/**
136
* Hook for infinite/paginated data fetching (only available for procedures with cursor input)
137
* @param input - Input parameters excluding cursor and direction
138
* @param opts - Infinite query configuration options
139
* @returns Infinite query result with pages data and pagination controls
140
*/
141
procedure.useInfiniteQuery<TData = InfiniteData<TOutput>>(
142
input: Omit<TInput, 'cursor' | 'direction'> | SkipToken,
143
opts?: UseTRPCInfiniteQueryOptions<TOutput, TData, TError, TCursor>
144
): UseTRPCInfiniteQueryResult<TData, TError>;
145
146
interface UseTRPCInfiniteQueryOptions<TOutput, TData, TError, TCursor>
147
extends Omit<UseInfiniteQueryOptions<TOutput, TError, TData>, 'queryKey' | 'initialPageParam'> {
148
trpc?: TRPCReactRequestOptions;
149
initialCursor?: TCursor;
150
}
151
152
interface UseTRPCInfiniteQueryResult<TData, TError>
153
extends UseInfiniteQueryResult<TData, TError> {
154
trpc: TRPCHookResult;
155
}
156
```
157
158
**Usage Examples:**
159
160
```typescript
161
function PostsList() {
162
// Infinite query for paginated posts
163
const {
164
data,
165
fetchNextPage,
166
fetchPreviousPage,
167
hasNextPage,
168
hasPreviousPage,
169
isFetchingNextPage,
170
isFetchingPreviousPage,
171
} = trpc.posts.list.useInfiniteQuery(
172
{ limit: 10 },
173
{
174
getNextPageParam: (lastPage) => lastPage.nextCursor,
175
getPreviousPageParam: (firstPage) => firstPage.prevCursor,
176
initialCursor: null,
177
}
178
);
179
180
return (
181
<div>
182
<button
183
onClick={() => fetchPreviousPage()}
184
disabled={!hasPreviousPage || isFetchingPreviousPage}
185
>
186
Load Previous
187
</button>
188
189
{data?.pages.map((page, i) => (
190
<div key={i}>
191
{page.posts.map((post) => (
192
<article key={post.id}>
193
<h3>{post.title}</h3>
194
<p>{post.content}</p>
195
</article>
196
))}
197
</div>
198
))}
199
200
<button
201
onClick={() => fetchNextPage()}
202
disabled={!hasNextPage || isFetchingNextPage}
203
>
204
{isFetchingNextPage ? "Loading..." : "Load More"}
205
</button>
206
</div>
207
);
208
}
209
```
210
211
### useSuspenseInfiniteQuery
212
213
Suspense-enabled infinite query hook for paginated data with automatic suspense handling.
214
215
```typescript { .api }
216
/**
217
* Suspense-enabled infinite query hook (only available for procedures with cursor input)
218
* @param input - Input parameters excluding cursor and direction
219
* @param opts - Suspense infinite query configuration options
220
* @returns Tuple with infinite data and query result
221
*/
222
procedure.useSuspenseInfiniteQuery(
223
input: Omit<TInput, 'cursor' | 'direction'>,
224
opts: UseTRPCSuspenseInfiniteQueryOptions<TOutput, TCursor>
225
): [InfiniteData<TOutput>, UseSuspenseInfiniteQueryResult<InfiniteData<TOutput>, TError> & TRPCHookResult];
226
227
interface UseTRPCSuspenseInfiniteQueryOptions<TOutput, TCursor>
228
extends Omit<UseSuspenseInfiniteQueryOptions<TOutput, TError, InfiniteData<TOutput>>, 'queryKey' | 'initialPageParam'> {
229
trpc?: TRPCReactRequestOptions;
230
initialCursor?: TCursor;
231
}
232
```
233
234
**Usage Examples:**
235
236
```typescript
237
import { Suspense } from "react";
238
239
function PostsListSuspense() {
240
const [data, query] = trpc.posts.list.useSuspenseInfiniteQuery(
241
{ limit: 10 },
242
{
243
getNextPageParam: (lastPage) => lastPage.nextCursor,
244
}
245
);
246
247
return (
248
<div>
249
{data.pages.map((page, i) => (
250
<div key={i}>
251
{page.posts.map((post) => (
252
<article key={post.id}>
253
<h3>{post.title}</h3>
254
<p>{post.content}</p>
255
</article>
256
))}
257
</div>
258
))}
259
260
<button
261
onClick={() => query.fetchNextPage()}
262
disabled={!query.hasNextPage}
263
>
264
Load More
265
</button>
266
</div>
267
);
268
}
269
270
function App() {
271
return (
272
<Suspense fallback={<div>Loading posts...</div>}>
273
<PostsListSuspense />
274
</Suspense>
275
);
276
}
277
```
278
279
### usePrefetchQuery
280
281
Hook for prefetching query data without rendering the result.
282
283
```typescript { .api }
284
/**
285
* Hook for prefetching query data
286
* @param input - Input parameters for the procedure
287
* @param opts - Prefetch configuration options
288
*/
289
procedure.usePrefetchQuery(
290
input: TInput | SkipToken,
291
opts?: TRPCFetchQueryOptions<TOutput, TError>
292
): void;
293
294
interface TRPCFetchQueryOptions<TData, TError>
295
extends Omit<FetchQueryOptions<TData, TError>, 'queryKey'> {
296
trpc?: TRPCReactRequestOptions;
297
}
298
```
299
300
**Usage Examples:**
301
302
```typescript
303
function UserList() {
304
const { data: users } = trpc.users.list.useQuery();
305
306
// Prefetch user details on hover
307
const handleUserHover = (userId: number) => {
308
trpc.user.get.usePrefetchQuery({ id: userId });
309
};
310
311
return (
312
<ul>
313
{users?.map((user) => (
314
<li
315
key={user.id}
316
onMouseEnter={() => handleUserHover(user.id)}
317
>
318
<Link to={`/users/${user.id}`}>{user.name}</Link>
319
</li>
320
))}
321
</ul>
322
);
323
}
324
```
325
326
### usePrefetchInfiniteQuery
327
328
Hook for prefetching infinite query data.
329
330
```typescript { .api }
331
/**
332
* Hook for prefetching infinite query data (only available for procedures with cursor input)
333
* @param input - Input parameters excluding cursor and direction
334
* @param opts - Prefetch infinite query configuration options
335
*/
336
procedure.usePrefetchInfiniteQuery(
337
input: Omit<TInput, 'cursor' | 'direction'> | SkipToken,
338
opts: TRPCFetchInfiniteQueryOptions<TInput, TOutput, TError>
339
): void;
340
341
interface TRPCFetchInfiniteQueryOptions<TInput, TOutput, TError>
342
extends Omit<FetchInfiniteQueryOptions<TOutput, TError>, 'queryKey' | 'initialPageParam'> {
343
trpc?: TRPCReactRequestOptions;
344
initialCursor?: ExtractCursorType<TInput>;
345
}
346
```
347
348
**Usage Examples:**
349
350
```typescript
351
function PostsNavigation() {
352
// Prefetch first page of posts
353
trpc.posts.list.usePrefetchInfiniteQuery(
354
{ limit: 10 },
355
{
356
initialCursor: null,
357
}
358
);
359
360
return (
361
<nav>
362
<Link to="/posts">View Posts</Link>
363
</nav>
364
);
365
}
366
```
367
368
## Common Patterns
369
370
### Conditional Queries
371
372
```typescript
373
function ConditionalQuery({ userId }: { userId?: number }) {
374
// Skip query when userId is undefined
375
const { data } = trpc.user.get.useQuery(
376
userId ? { id: userId } : skipToken
377
);
378
379
// Alternative with enabled option
380
const { data: user } = trpc.user.get.useQuery(
381
{ id: userId! },
382
{ enabled: !!userId }
383
);
384
385
return <div>{data?.name}</div>;
386
}
387
```
388
389
### Error Handling
390
391
```typescript
392
function UserWithErrorHandling({ userId }: { userId: number }) {
393
const { data, error, isError } = trpc.user.get.useQuery({ id: userId });
394
395
if (isError) {
396
if (error.data?.code === "NOT_FOUND") {
397
return <div>User not found</div>;
398
}
399
return <div>Error: {error.message}</div>;
400
}
401
402
return <div>{data?.name}</div>;
403
}
404
```
405
406
### Data Transformation
407
408
```typescript
409
function TransformedQuery({ userId }: { userId: number }) {
410
// Transform data in the select function
411
const { data: userInfo } = trpc.user.get.useQuery(
412
{ id: userId },
413
{
414
select: (user) => ({
415
displayName: `${user.firstName} ${user.lastName}`,
416
initials: `${user.firstName[0]}${user.lastName[0]}`,
417
}),
418
}
419
);
420
421
return <div>{userInfo?.displayName}</div>;
422
}
423
```