0
# Browser Integration
1
2
Browser event management for automatic refetching on focus and network reconnection with customizable event handling, background sync, and visibility-based optimizations.
3
4
## Capabilities
5
6
### Focus Manager
7
8
Manages window focus state for automatic query refetching when the application regains focus.
9
10
```typescript { .api }
11
/**
12
* Global focus manager instance
13
* Handles window focus/blur events and triggers query refetches
14
*/
15
const focusManager: {
16
/**
17
* Set up custom focus event handling
18
* @param setup - Function to set up custom focus listeners
19
*/
20
setEventListener(setup: (setFocused: (focused?: boolean) => void) => () => void): void;
21
22
/**
23
* Manually set the focused state
24
* @param focused - Whether the application is focused (undefined = auto-detect)
25
*/
26
setFocused(focused?: boolean): void;
27
28
/**
29
* Trigger focus event manually
30
* Causes queries configured for refetchOnWindowFocus to refetch
31
*/
32
onFocus(): void;
33
34
/**
35
* Check if the application is currently focused
36
* @returns true if application has focus
37
*/
38
isFocused(): boolean;
39
};
40
```
41
42
**Usage Examples:**
43
44
```typescript
45
import { focusManager } from "@tanstack/query-core";
46
47
// Basic usage - uses default window focus events
48
console.log('Is focused:', focusManager.isFocused());
49
50
// Manual focus control
51
focusManager.setFocused(true);
52
focusManager.setFocused(false);
53
54
// Trigger refetch manually
55
focusManager.onFocus();
56
57
// Custom focus event handling
58
focusManager.setEventListener((setFocused) => {
59
// Custom logic for determining focus state
60
const handleVisibilityChange = () => {
61
setFocused(!document.hidden);
62
};
63
64
const handleFocus = () => setFocused(true);
65
const handleBlur = () => setFocused(false);
66
67
// Set up listeners
68
document.addEventListener('visibilitychange', handleVisibilityChange);
69
window.addEventListener('focus', handleFocus);
70
window.addEventListener('blur', handleBlur);
71
72
// Return cleanup function
73
return () => {
74
document.removeEventListener('visibilitychange', handleVisibilityChange);
75
window.removeEventListener('focus', handleFocus);
76
window.removeEventListener('blur', handleBlur);
77
};
78
});
79
80
// React Native focus handling
81
focusManager.setEventListener((setFocused) => {
82
const subscription = AppState.addEventListener('change', (state) => {
83
setFocused(state === 'active');
84
});
85
86
return () => subscription?.remove();
87
});
88
89
// Electron focus handling
90
focusManager.setEventListener((setFocused) => {
91
const { ipcRenderer } = require('electron');
92
93
const handleFocus = () => setFocused(true);
94
const handleBlur = () => setFocused(false);
95
96
ipcRenderer.on('focus', handleFocus);
97
ipcRenderer.on('blur', handleBlur);
98
99
return () => {
100
ipcRenderer.off('focus', handleFocus);
101
ipcRenderer.off('blur', handleBlur);
102
};
103
});
104
```
105
106
### Online Manager
107
108
Manages network connectivity state for automatic query refetching when connection is restored.
109
110
```typescript { .api }
111
/**
112
* Global online manager instance
113
* Handles network online/offline events and triggers query refetches
114
*/
115
const onlineManager: {
116
/**
117
* Set up custom online event handling
118
* @param setup - Function to set up custom online listeners
119
*/
120
setEventListener(setup: (setOnline: (online?: boolean) => void) => () => void): void;
121
122
/**
123
* Manually set the online state
124
* @param online - Whether the application is online (undefined = auto-detect)
125
*/
126
setOnline(online?: boolean): void;
127
128
/**
129
* Check if the application is currently online
130
* @returns true if application is online
131
*/
132
isOnline(): boolean;
133
};
134
```
135
136
**Usage Examples:**
137
138
```typescript
139
import { onlineManager } from "@tanstack/query-core";
140
141
// Basic usage - uses default navigator.onLine
142
console.log('Is online:', onlineManager.isOnline());
143
144
// Manual online control
145
onlineManager.setOnline(true);
146
onlineManager.setOnline(false);
147
148
// Custom online detection
149
onlineManager.setEventListener((setOnline) => {
150
const handleOnline = () => setOnline(true);
151
const handleOffline = () => setOnline(false);
152
153
// Set up listeners
154
window.addEventListener('online', handleOnline);
155
window.addEventListener('offline', handleOffline);
156
157
// Return cleanup function
158
return () => {
159
window.removeEventListener('online', handleOnline);
160
window.removeEventListener('offline', handleOffline);
161
};
162
});
163
164
// React Native network detection
165
onlineManager.setEventListener((setOnline) => {
166
const NetInfo = require('@react-native-async-storage/async-storage');
167
168
const unsubscribe = NetInfo.addEventListener((state) => {
169
setOnline(state.isConnected);
170
});
171
172
return unsubscribe;
173
});
174
175
// Advanced network quality detection
176
onlineManager.setEventListener((setOnline) => {
177
let isOnline = navigator.onLine;
178
179
// Test actual connectivity periodically
180
const testConnectivity = async () => {
181
try {
182
const response = await fetch('/api/ping', {
183
method: 'HEAD',
184
cache: 'no-cache',
185
});
186
const newOnlineState = response.ok;
187
if (newOnlineState !== isOnline) {
188
isOnline = newOnlineState;
189
setOnline(isOnline);
190
}
191
} catch {
192
if (isOnline) {
193
isOnline = false;
194
setOnline(false);
195
}
196
}
197
};
198
199
// Check connectivity every 30 seconds
200
const interval = setInterval(testConnectivity, 30000);
201
202
// Also listen to browser events
203
const handleOnline = () => {
204
isOnline = true;
205
setOnline(true);
206
testConnectivity(); // Verify actual connectivity
207
};
208
209
const handleOffline = () => {
210
isOnline = false;
211
setOnline(false);
212
};
213
214
window.addEventListener('online', handleOnline);
215
window.addEventListener('offline', handleOffline);
216
217
return () => {
218
clearInterval(interval);
219
window.removeEventListener('online', handleOnline);
220
window.removeEventListener('offline', handleOffline);
221
};
222
});
223
```
224
225
### Notification Manager
226
227
Manages notification batching and scheduling for optimal performance.
228
229
```typescript { .api }
230
/**
231
* Global notification manager instance
232
* Handles batching and scheduling of state updates
233
*/
234
const notifyManager: {
235
/**
236
* Batch multiple operations to reduce re-renders
237
* @param callback - Function containing operations to batch
238
* @returns Return value of the callback
239
*/
240
batch<T>(callback: () => T): T;
241
242
/**
243
* Create a batched version of a function
244
* @param callback - Function to make batched
245
* @returns Batched version of the function
246
*/
247
batchCalls<TArgs extends ReadonlyArray<unknown>, TResponse>(
248
callback: (...args: TArgs) => TResponse
249
): (...args: TArgs) => TResponse;
250
251
/**
252
* Schedule a notification to be processed
253
* @param callback - Function to schedule
254
*/
255
schedule(callback: () => void): void;
256
257
/**
258
* Set a custom notification function
259
* @param fn - Custom notification function
260
*/
261
setNotifyFunction(fn: (callback: () => void) => void): void;
262
263
/**
264
* Set a custom batch notification function
265
* @param fn - Custom batch notification function
266
*/
267
setBatchNotifyFunction(fn: (callback: () => void) => void): void;
268
269
/**
270
* Set a custom scheduler function
271
* @param fn - Custom scheduler function
272
*/
273
setScheduler(fn: (callback: () => void) => void): void;
274
};
275
276
/**
277
* Default scheduler function that uses setTimeout
278
*/
279
const defaultScheduler: (callback: () => void) => void;
280
```
281
282
**Usage Examples:**
283
284
```typescript
285
import { notifyManager, defaultScheduler } from "@tanstack/query-core";
286
287
// Batch multiple operations
288
notifyManager.batch(() => {
289
// Multiple state updates will be batched together
290
queryClient.setQueryData(['user', 1], userData1);
291
queryClient.setQueryData(['user', 2], userData2);
292
queryClient.setQueryData(['user', 3], userData3);
293
// Only one re-render will occur after this batch
294
});
295
296
// Create batched functions
297
const batchedUpdate = notifyManager.batchCalls((data) => {
298
// This function will be batched automatically
299
updateUI(data);
300
});
301
302
// Multiple calls will be batched
303
batchedUpdate(data1);
304
batchedUpdate(data2);
305
batchedUpdate(data3);
306
307
// Custom notification handling for React
308
notifyManager.setNotifyFunction((callback) => {
309
// Use React's batch updates
310
ReactDOM.unstable_batchedUpdates(callback);
311
});
312
313
// Custom scheduling with requestAnimationFrame
314
notifyManager.setScheduler((callback) => {
315
requestAnimationFrame(callback);
316
});
317
318
// Custom batch notification for performance
319
notifyManager.setBatchNotifyFunction((callback) => {
320
// Debounce notifications
321
clearTimeout(batchTimer);
322
batchTimer = setTimeout(callback, 0);
323
});
324
325
// React Concurrent Mode integration
326
notifyManager.setNotifyFunction((callback) => {
327
if (typeof React !== 'undefined' && React.startTransition) {
328
React.startTransition(callback);
329
} else {
330
callback();
331
}
332
});
333
```
334
335
### Background Sync Integration
336
337
Implementing background synchronization with browser APIs.
338
339
```typescript { .api }
340
// Service Worker integration for background sync
341
class BackgroundSyncManager {
342
private queryClient: QueryClient;
343
344
constructor(queryClient: QueryClient) {
345
this.queryClient = queryClient;
346
this.setupBackgroundSync();
347
}
348
349
private setupBackgroundSync() {
350
// Register service worker
351
if ('serviceWorker' in navigator) {
352
navigator.serviceWorker.register('/sw.js');
353
}
354
355
// Listen for focus events
356
focusManager.setEventListener((setFocused) => {
357
const handleVisibilityChange = () => {
358
const isVisible = !document.hidden;
359
setFocused(isVisible);
360
361
if (isVisible) {
362
// App became visible, sync data
363
this.syncOnForeground();
364
}
365
};
366
367
document.addEventListener('visibilitychange', handleVisibilityChange);
368
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
369
});
370
371
// Listen for online events
372
onlineManager.setEventListener((setOnline) => {
373
const handleOnline = () => {
374
setOnline(true);
375
this.syncOnReconnect();
376
};
377
378
const handleOffline = () => setOnline(false);
379
380
window.addEventListener('online', handleOnline);
381
window.addEventListener('offline', handleOffline);
382
383
return () => {
384
window.removeEventListener('online', handleOnline);
385
window.removeEventListener('offline', handleOffline);
386
};
387
});
388
}
389
390
private async syncOnForeground() {
391
// Refetch critical data when app comes to foreground
392
await this.queryClient.refetchQueries({
393
type: 'active',
394
stale: true,
395
});
396
}
397
398
private async syncOnReconnect() {
399
// Resume paused mutations and refetch failed queries
400
await Promise.all([
401
this.queryClient.resumePausedMutations(),
402
this.queryClient.refetchQueries({
403
predicate: (query) => query.state.fetchStatus === 'paused',
404
}),
405
]);
406
}
407
408
// Background sync for mutations
409
syncMutation(mutationKey: string, data: unknown) {
410
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
411
navigator.serviceWorker.controller.postMessage({
412
type: 'BACKGROUND_SYNC',
413
mutationKey,
414
data,
415
});
416
}
417
}
418
}
419
420
// Usage
421
const backgroundSync = new BackgroundSyncManager(queryClient);
422
```
423
424
### Page Lifecycle Integration
425
426
Advanced integration with Page Lifecycle API for better resource management.
427
428
```typescript { .api }
429
class PageLifecycleManager {
430
constructor(private queryClient: QueryClient) {
431
this.setupPageLifecycle();
432
}
433
434
private setupPageLifecycle() {
435
// Page Lifecycle API integration
436
if ('onfreeze' in document) {
437
document.addEventListener('freeze', this.handleFreeze.bind(this));
438
document.addEventListener('resume', this.handleResume.bind(this));
439
}
440
441
// Fallback for browsers without Page Lifecycle API
442
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
443
window.addEventListener('beforeunload', this.handleBeforeUnload.bind(this));
444
window.addEventListener('pagehide', this.handlePageHide.bind(this));
445
window.addEventListener('pageshow', this.handlePageShow.bind(this));
446
}
447
448
private handleFreeze() {
449
// Page is being frozen (backgrounded on mobile)
450
console.log('Page frozen - pausing queries');
451
452
// Cancel ongoing requests to save battery
453
this.queryClient.cancelQueries();
454
455
// Persist important cache data
456
const dehydratedState = dehydrate(this.queryClient);
457
try {
458
sessionStorage.setItem('query-cache-freeze', JSON.stringify(dehydratedState));
459
} catch (e) {
460
console.warn('Failed to persist cache on freeze');
461
}
462
}
463
464
private handleResume() {
465
// Page is being resumed
466
console.log('Page resumed - resuming queries');
467
468
// Restore cache if needed
469
try {
470
const stored = sessionStorage.getItem('query-cache-freeze');
471
if (stored) {
472
const state = JSON.parse(stored);
473
hydrate(this.queryClient, state);
474
sessionStorage.removeItem('query-cache-freeze');
475
}
476
} catch (e) {
477
console.warn('Failed to restore cache on resume');
478
}
479
480
// Refetch stale data
481
this.queryClient.refetchQueries({ stale: true });
482
}
483
484
private handleVisibilityChange() {
485
if (document.hidden) {
486
// Page hidden - reduce activity
487
this.reduceActivity();
488
} else {
489
// Page visible - resume normal activity
490
this.resumeActivity();
491
}
492
}
493
494
private handleBeforeUnload() {
495
// Page is about to unload - cleanup
496
this.queryClient.cancelQueries();
497
}
498
499
private handlePageHide(event: PageTransitionEvent) {
500
if (event.persisted) {
501
// Page is going into back/forward cache
502
this.handleFreeze();
503
}
504
}
505
506
private handlePageShow(event: PageTransitionEvent) {
507
if (event.persisted) {
508
// Page is coming back from back/forward cache
509
this.handleResume();
510
}
511
}
512
513
private reduceActivity() {
514
// Reduce query frequency when page is hidden
515
const queries = this.queryClient.getQueryCache().getAll();
516
517
queries.forEach(query => {
518
if (query.observers.length === 0) {
519
// Stop background refetching for inactive queries
520
query.destroy();
521
}
522
});
523
}
524
525
private resumeActivity() {
526
// Resume normal query activity when page is visible
527
this.queryClient.refetchQueries({
528
type: 'active',
529
predicate: (query) => {
530
// Only refetch if data is stale or hasn't been fetched recently
531
const staleTime = query.options.staleTime ?? 0;
532
return Date.now() - query.state.dataUpdatedAt > staleTime;
533
},
534
});
535
}
536
}
537
538
// Usage
539
const lifecycleManager = new PageLifecycleManager(queryClient);
540
```
541
542
## Core Types
543
544
```typescript { .api }
545
type SetupFn = (setEventState: (state?: boolean) => void) => (() => void) | void;
546
547
interface NotifyFunction {
548
(callback: () => void): void;
549
}
550
551
interface BatchNotifyFunction {
552
(callback: () => void): void;
553
}
554
555
interface ScheduleFunction {
556
(callback: () => void): void;
557
}
558
```