0
# Query Observers
1
2
Reactive observers for tracking query state changes with automatic updates, optimistic results, lifecycle management, and intelligent subscription handling.
3
4
## Capabilities
5
6
### QueryObserver
7
8
The primary observer class for tracking individual query state changes.
9
10
```typescript { .api }
11
/**
12
* Observer for tracking query state changes and managing subscriptions
13
* Provides reactive updates when query data, loading state, or errors change
14
*/
15
class QueryObserver<TData = unknown, TError = Error, TQueryData = TData, TQueryKey extends QueryKey = QueryKey> {
16
constructor(client: QueryClient, options: QueryObserverOptions<TData, TError, TQueryData, TQueryKey>);
17
18
/**
19
* Get the current result snapshot
20
* Returns the latest query state without subscribing
21
* @returns Current query observer result
22
*/
23
getCurrentResult(): QueryObserverResult<TData, TError>;
24
25
/**
26
* Get the current query instance
27
* @returns The underlying Query instance
28
*/
29
getCurrentQuery(): Query<TQueryData, TError, TData, TQueryKey>;
30
31
/**
32
* Subscribe to query state changes
33
* @param onStoreChange - Callback function called when state changes
34
* @returns Unsubscribe function
35
*/
36
subscribe(onStoreChange: (result: QueryObserverResult<TData, TError>) => void): () => void;
37
38
/**
39
* Update observer options
40
* @param options - New observer options to merge
41
*/
42
setOptions(options: QueryObserverOptions<TData, TError, TQueryData, TQueryKey>): void;
43
44
/**
45
* Get optimistic result for given options
46
* Useful for getting result before actually changing options
47
* @param options - Options to get optimistic result for
48
* @returns Optimistic query observer result
49
*/
50
getOptimisticResult(options: QueryObserverOptions<TData, TError, TQueryData, TQueryKey>): QueryObserverResult<TData, TError>;
51
52
/**
53
* Trigger a refetch of the query
54
* @param options - Refetch options
55
* @returns Promise resolving to the new result
56
*/
57
refetch(options?: RefetchOptions): Promise<QueryObserverResult<TData, TError>>;
58
59
/**
60
* Fetch with optimistic options
61
* @param options - Options for optimistic fetch
62
* @returns Promise resolving to the result
63
*/
64
fetchOptimistic(options: QueryObserverOptions<TData, TError, TQueryData, TQueryKey>): Promise<QueryObserverResult<TData, TError>>;
65
66
/**
67
* Destroy the observer and cleanup subscriptions
68
*/
69
destroy(): void;
70
71
/**
72
* Track specific properties of the result for selective updates
73
* @param result - Result to track
74
* @param onPropTracked - Callback when property is tracked
75
* @returns Tracked result
76
*/
77
trackResult(result: QueryObserverResult<TData, TError>, onPropTracked?: () => void): QueryObserverResult<TData, TError>;
78
79
/**
80
* Check if should refetch on window focus
81
* @returns true if should refetch on focus
82
*/
83
shouldFetchOnWindowFocus(): boolean;
84
85
/**
86
* Check if should refetch on reconnect
87
* @returns true if should refetch on reconnect
88
*/
89
shouldFetchOnReconnect(): boolean;
90
}
91
92
interface QueryObserverOptions<TData = unknown, TError = Error, TQueryData = TData, TQueryKey extends QueryKey = QueryKey> {
93
queryKey: TQueryKey;
94
queryFn?: QueryFunction<TQueryData, TQueryKey>;
95
enabled?: boolean | ((query: Query<TQueryData, TError, TData, TQueryKey>) => boolean);
96
networkMode?: NetworkMode;
97
initialData?: TData | InitialDataFunction<TData>;
98
initialDataUpdatedAt?: number | (() => number | undefined);
99
placeholderData?: TData | PlaceholderDataFunction<TData>;
100
staleTime?: number | ((query: Query<TQueryData, TError, TData, TQueryKey>) => number);
101
gcTime?: number;
102
refetchInterval?: number | false | ((data: TData | undefined, query: Query<TQueryData, TError, TData, TQueryKey>) => number | false);
103
refetchIntervalInBackground?: boolean;
104
refetchOnMount?: boolean | "always" | ((query: Query<TQueryData, TError, TData, TQueryKey>) => boolean | "always");
105
refetchOnWindowFocus?: boolean | "always" | ((query: Query<TQueryData, TError, TData, TQueryKey>) => boolean | "always");
106
refetchOnReconnect?: boolean | "always" | ((query: Query<TQueryData, TError, TData, TQueryKey>) => boolean | "always");
107
retry?: RetryValue<TError>;
108
retryDelay?: RetryDelayValue<TError>;
109
select?: (data: TQueryData) => TData;
110
throwOnError?: ThrowOnError<TQueryData, TError, TData, TQueryKey>;
111
meta?: QueryMeta;
112
structuralSharing?: boolean | ((oldData: TData | undefined, newData: TData) => TData);
113
}
114
115
interface QueryObserverResult<TData = unknown, TError = Error> {
116
data: TData | undefined;
117
dataUpdatedAt: number;
118
error: TError | null;
119
errorUpdatedAt: number;
120
failureCount: number;
121
failureReason: TError | null;
122
fetchStatus: FetchStatus;
123
isError: boolean;
124
isFetched: boolean;
125
isFetchedAfterMount: boolean;
126
isFetching: boolean;
127
isInitialLoading: boolean;
128
isLoading: boolean;
129
isLoadingError: boolean;
130
isPaused: boolean;
131
isPlaceholderData: boolean;
132
isPending: boolean;
133
isRefetching: boolean;
134
isRefetchError: boolean;
135
isStale: boolean;
136
isSuccess: boolean;
137
refetch: (options?: RefetchOptions) => Promise<QueryObserverResult<TData, TError>>;
138
status: QueryStatus;
139
}
140
```
141
142
**Usage Examples:**
143
144
```typescript
145
import { QueryClient, QueryObserver } from "@tanstack/query-core";
146
147
const queryClient = new QueryClient();
148
149
// Create observer
150
const observer = new QueryObserver(queryClient, {
151
queryKey: ['user', 123],
152
queryFn: async () => {
153
const response = await fetch('/api/user/123');
154
return response.json();
155
},
156
staleTime: 5 * 60 * 1000, // 5 minutes
157
refetchOnWindowFocus: true,
158
});
159
160
// Subscribe to changes
161
const unsubscribe = observer.subscribe((result) => {
162
console.log('Data:', result.data);
163
console.log('Loading:', result.isLoading);
164
console.log('Error:', result.error);
165
console.log('Status:', result.status);
166
167
if (result.isSuccess) {
168
console.log('User loaded successfully:', result.data);
169
}
170
171
if (result.isError) {
172
console.error('Failed to load user:', result.error);
173
}
174
});
175
176
// Get current result without subscribing
177
const currentResult = observer.getCurrentResult();
178
179
// Update options
180
observer.setOptions({
181
queryKey: ['user', 123],
182
queryFn: async () => {
183
const response = await fetch('/api/user/123?v=2');
184
return response.json();
185
},
186
staleTime: 10 * 60 * 1000, // 10 minutes
187
});
188
189
// Manual refetch
190
const newResult = await observer.refetch();
191
192
// Cleanup
193
unsubscribe();
194
observer.destroy();
195
```
196
197
### QueriesObserver
198
199
Observer for tracking multiple queries simultaneously.
200
201
```typescript { .api }
202
/**
203
* Observer for tracking multiple queries simultaneously
204
* Provides combined results and manages multiple query subscriptions
205
*/
206
class QueriesObserver<TCombinedResult = QueryObserverResult[]> {
207
constructor(
208
client: QueryClient,
209
queries: QueryObserverOptionsForCreateQueries[],
210
options?: QueriesObserverOptions<TCombinedResult>
211
);
212
213
/**
214
* Update the queries being observed
215
* @param queries - New array of query options
216
* @param options - Observer options
217
*/
218
setQueries(
219
queries: QueryObserverOptionsForCreateQueries[],
220
options?: QueriesObserverOptions<TCombinedResult>
221
): void;
222
223
/**
224
* Get current combined result from all queries
225
* @returns Combined result from all observed queries
226
*/
227
getCurrentResult(): TCombinedResult;
228
229
/**
230
* Get optimistic combined result for given queries
231
* @param queries - Query options to get optimistic result for
232
* @param options - Observer options
233
* @returns Optimistic combined result
234
*/
235
getOptimisticResult(
236
queries: QueryObserverOptionsForCreateQueries[],
237
options?: QueriesObserverOptions<TCombinedResult>
238
): TCombinedResult;
239
240
/**
241
* Subscribe to changes in any of the observed queries
242
* @param onStoreChange - Callback called when any query changes
243
* @returns Unsubscribe function
244
*/
245
subscribe(onStoreChange: (result: TCombinedResult) => void): () => void;
246
247
/**
248
* Destroy the observer and cleanup all subscriptions
249
*/
250
destroy(): void;
251
}
252
253
interface QueriesObserverOptions<TCombinedResult> {
254
combine?: (results: QueryObserverResult[]) => TCombinedResult;
255
}
256
257
type QueryObserverOptionsForCreateQueries = QueryObserverOptions & {
258
queryKey: QueryKey;
259
};
260
```
261
262
**Usage Examples:**
263
264
```typescript
265
import { QueryClient, QueriesObserver } from "@tanstack/query-core";
266
267
const queryClient = new QueryClient();
268
269
// Create queries observer
270
const queriesObserver = new QueriesObserver(queryClient, [
271
{
272
queryKey: ['user', 123],
273
queryFn: async () => {
274
const response = await fetch('/api/user/123');
275
return response.json();
276
},
277
},
278
{
279
queryKey: ['posts', 123],
280
queryFn: async () => {
281
const response = await fetch('/api/user/123/posts');
282
return response.json();
283
},
284
},
285
{
286
queryKey: ['settings', 123],
287
queryFn: async () => {
288
const response = await fetch('/api/user/123/settings');
289
return response.json();
290
},
291
},
292
], {
293
combine: (results) => ({
294
user: results[0],
295
posts: results[1],
296
settings: results[2],
297
isLoading: results.some(result => result.isLoading),
298
hasError: results.some(result => result.isError),
299
}),
300
});
301
302
// Subscribe to combined changes
303
const unsubscribe = queriesObserver.subscribe((result) => {
304
console.log('User data:', result.user.data);
305
console.log('Posts data:', result.posts.data);
306
console.log('Settings data:', result.settings.data);
307
console.log('Any loading:', result.isLoading);
308
console.log('Any error:', result.hasError);
309
});
310
311
// Update queries
312
queriesObserver.setQueries([
313
{
314
queryKey: ['user', 456], // Different user
315
queryFn: async () => {
316
const response = await fetch('/api/user/456');
317
return response.json();
318
},
319
},
320
{
321
queryKey: ['posts', 456],
322
queryFn: async () => {
323
const response = await fetch('/api/user/456/posts');
324
return response.json();
325
},
326
},
327
]);
328
329
// Cleanup
330
unsubscribe();
331
queriesObserver.destroy();
332
```
333
334
### Result Properties
335
336
Understanding the properties available in query observer results.
337
338
```typescript { .api }
339
interface QueryObserverResult<TData = unknown, TError = Error> {
340
/** The last successfully resolved data for the query */
341
data: TData | undefined;
342
343
/** The timestamp for when the query was most recently returned in a resolved state */
344
dataUpdatedAt: number;
345
346
/** The error object for the query, if an error was thrown */
347
error: TError | null;
348
349
/** The timestamp for when the query most recently returned the error status */
350
errorUpdatedAt: number;
351
352
/** The failure count for the query */
353
failureCount: number;
354
355
/** The failure reason for the query retry */
356
failureReason: TError | null;
357
358
/** The fetch status of the query */
359
fetchStatus: FetchStatus;
360
361
/** Will be true if the query has been fetched */
362
isFetched: boolean;
363
364
/** Will be true if the query has been fetched after the component mounted */
365
isFetchedAfterMount: boolean;
366
367
/** Will be true if the query is currently fetching */
368
isFetching: boolean;
369
370
/** Will be true if the query failed while fetching for the first time */
371
isLoadingError: boolean;
372
373
/** Will be true if the query is currently paused */
374
isPaused: boolean;
375
376
/** Will be true if the data shown is placeholder data */
377
isPlaceholderData: boolean;
378
379
/** Will be true if the query is currently refetching */
380
isRefetching: boolean;
381
382
/** Will be true if the query failed while refetching */
383
isRefetchError: boolean;
384
385
/** Will be true if the query data is stale */
386
isStale: boolean;
387
388
/** Derived booleans from status */
389
isError: boolean;
390
isInitialLoading: boolean;
391
isLoading: boolean;
392
isPending: boolean;
393
isSuccess: boolean;
394
395
/** Function to manually refetch the query */
396
refetch: (options?: RefetchOptions) => Promise<QueryObserverResult<TData, TError>>;
397
398
/** The status of the query */
399
status: QueryStatus;
400
}
401
```
402
403
### Observer Configuration
404
405
Advanced configuration options for query observers.
406
407
```typescript { .api }
408
type PlaceholderDataFunction<T> = (previousValue: T | undefined, previousQuery: Query | undefined) => T;
409
410
type ThrowOnError<TQueryFnData, TError, TData, TQueryKey extends QueryKey> =
411
| boolean
412
| ((error: TError, query: Query<TQueryFnData, TError, TData, TQueryKey>) => boolean);
413
414
interface RefetchOptions extends CancelOptions {
415
throwOnError?: boolean;
416
}
417
418
interface CancelOptions {
419
revert?: boolean;
420
silent?: boolean;
421
}
422
```
423
424
## Core Types
425
426
```typescript { .api }
427
type QueryStatus = 'pending' | 'error' | 'success';
428
type FetchStatus = 'fetching' | 'paused' | 'idle';
429
type NetworkMode = 'online' | 'always' | 'offlineFirst';
430
431
type RetryValue<TError> = boolean | number | ((failureCount: number, error: TError) => boolean);
432
type RetryDelayValue<TError> = number | ((failureCount: number, error: TError, query: Query) => number);
433
434
type InitialDataFunction<T> = () => T | undefined;
435
436
interface QueryMeta extends Record<string, unknown> {}
437
```