The framework agnostic core that powers TanStack Query for data fetching and caching
—
Browser event management for automatic refetching on focus and network reconnection with customizable event handling, background sync, and visibility-based optimizations.
Manages window focus state for automatic query refetching when the application regains focus.
/**
* Global focus manager instance
* Handles window focus/blur events and triggers query refetches
*/
const focusManager: {
/**
* Set up custom focus event handling
* @param setup - Function to set up custom focus listeners
*/
setEventListener(setup: (setFocused: (focused?: boolean) => void) => () => void): void;
/**
* Manually set the focused state
* @param focused - Whether the application is focused (undefined = auto-detect)
*/
setFocused(focused?: boolean): void;
/**
* Trigger focus event manually
* Causes queries configured for refetchOnWindowFocus to refetch
*/
onFocus(): void;
/**
* Check if the application is currently focused
* @returns true if application has focus
*/
isFocused(): boolean;
};Usage Examples:
import { focusManager } from "@tanstack/query-core";
// Basic usage - uses default window focus events
console.log('Is focused:', focusManager.isFocused());
// Manual focus control
focusManager.setFocused(true);
focusManager.setFocused(false);
// Trigger refetch manually
focusManager.onFocus();
// Custom focus event handling
focusManager.setEventListener((setFocused) => {
// Custom logic for determining focus state
const handleVisibilityChange = () => {
setFocused(!document.hidden);
};
const handleFocus = () => setFocused(true);
const handleBlur = () => setFocused(false);
// Set up listeners
document.addEventListener('visibilitychange', handleVisibilityChange);
window.addEventListener('focus', handleFocus);
window.addEventListener('blur', handleBlur);
// Return cleanup function
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
window.removeEventListener('focus', handleFocus);
window.removeEventListener('blur', handleBlur);
};
});
// React Native focus handling
focusManager.setEventListener((setFocused) => {
const subscription = AppState.addEventListener('change', (state) => {
setFocused(state === 'active');
});
return () => subscription?.remove();
});
// Electron focus handling
focusManager.setEventListener((setFocused) => {
const { ipcRenderer } = require('electron');
const handleFocus = () => setFocused(true);
const handleBlur = () => setFocused(false);
ipcRenderer.on('focus', handleFocus);
ipcRenderer.on('blur', handleBlur);
return () => {
ipcRenderer.off('focus', handleFocus);
ipcRenderer.off('blur', handleBlur);
};
});Manages network connectivity state for automatic query refetching when connection is restored.
/**
* Global online manager instance
* Handles network online/offline events and triggers query refetches
*/
const onlineManager: {
/**
* Set up custom online event handling
* @param setup - Function to set up custom online listeners
*/
setEventListener(setup: (setOnline: (online?: boolean) => void) => () => void): void;
/**
* Manually set the online state
* @param online - Whether the application is online (undefined = auto-detect)
*/
setOnline(online?: boolean): void;
/**
* Check if the application is currently online
* @returns true if application is online
*/
isOnline(): boolean;
};Usage Examples:
import { onlineManager } from "@tanstack/query-core";
// Basic usage - uses default navigator.onLine
console.log('Is online:', onlineManager.isOnline());
// Manual online control
onlineManager.setOnline(true);
onlineManager.setOnline(false);
// Custom online detection
onlineManager.setEventListener((setOnline) => {
const handleOnline = () => setOnline(true);
const handleOffline = () => setOnline(false);
// Set up listeners
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
// Return cleanup function
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
// React Native network detection
onlineManager.setEventListener((setOnline) => {
const NetInfo = require('@react-native-async-storage/async-storage');
const unsubscribe = NetInfo.addEventListener((state) => {
setOnline(state.isConnected);
});
return unsubscribe;
});
// Advanced network quality detection
onlineManager.setEventListener((setOnline) => {
let isOnline = navigator.onLine;
// Test actual connectivity periodically
const testConnectivity = async () => {
try {
const response = await fetch('/api/ping', {
method: 'HEAD',
cache: 'no-cache',
});
const newOnlineState = response.ok;
if (newOnlineState !== isOnline) {
isOnline = newOnlineState;
setOnline(isOnline);
}
} catch {
if (isOnline) {
isOnline = false;
setOnline(false);
}
}
};
// Check connectivity every 30 seconds
const interval = setInterval(testConnectivity, 30000);
// Also listen to browser events
const handleOnline = () => {
isOnline = true;
setOnline(true);
testConnectivity(); // Verify actual connectivity
};
const handleOffline = () => {
isOnline = false;
setOnline(false);
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
clearInterval(interval);
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});Manages notification batching and scheduling for optimal performance.
/**
* Global notification manager instance
* Handles batching and scheduling of state updates
*/
const notifyManager: {
/**
* Batch multiple operations to reduce re-renders
* @param callback - Function containing operations to batch
* @returns Return value of the callback
*/
batch<T>(callback: () => T): T;
/**
* Create a batched version of a function
* @param callback - Function to make batched
* @returns Batched version of the function
*/
batchCalls<TArgs extends ReadonlyArray<unknown>, TResponse>(
callback: (...args: TArgs) => TResponse
): (...args: TArgs) => TResponse;
/**
* Schedule a notification to be processed
* @param callback - Function to schedule
*/
schedule(callback: () => void): void;
/**
* Set a custom notification function
* @param fn - Custom notification function
*/
setNotifyFunction(fn: (callback: () => void) => void): void;
/**
* Set a custom batch notification function
* @param fn - Custom batch notification function
*/
setBatchNotifyFunction(fn: (callback: () => void) => void): void;
/**
* Set a custom scheduler function
* @param fn - Custom scheduler function
*/
setScheduler(fn: (callback: () => void) => void): void;
};
/**
* Default scheduler function that uses setTimeout
*/
const defaultScheduler: (callback: () => void) => void;Usage Examples:
import { notifyManager, defaultScheduler } from "@tanstack/query-core";
// Batch multiple operations
notifyManager.batch(() => {
// Multiple state updates will be batched together
queryClient.setQueryData(['user', 1], userData1);
queryClient.setQueryData(['user', 2], userData2);
queryClient.setQueryData(['user', 3], userData3);
// Only one re-render will occur after this batch
});
// Create batched functions
const batchedUpdate = notifyManager.batchCalls((data) => {
// This function will be batched automatically
updateUI(data);
});
// Multiple calls will be batched
batchedUpdate(data1);
batchedUpdate(data2);
batchedUpdate(data3);
// Custom notification handling for React
notifyManager.setNotifyFunction((callback) => {
// Use React's batch updates
ReactDOM.unstable_batchedUpdates(callback);
});
// Custom scheduling with requestAnimationFrame
notifyManager.setScheduler((callback) => {
requestAnimationFrame(callback);
});
// Custom batch notification for performance
notifyManager.setBatchNotifyFunction((callback) => {
// Debounce notifications
clearTimeout(batchTimer);
batchTimer = setTimeout(callback, 0);
});
// React Concurrent Mode integration
notifyManager.setNotifyFunction((callback) => {
if (typeof React !== 'undefined' && React.startTransition) {
React.startTransition(callback);
} else {
callback();
}
});Implementing background synchronization with browser APIs.
// Service Worker integration for background sync
class BackgroundSyncManager {
private queryClient: QueryClient;
constructor(queryClient: QueryClient) {
this.queryClient = queryClient;
this.setupBackgroundSync();
}
private setupBackgroundSync() {
// Register service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// Listen for focus events
focusManager.setEventListener((setFocused) => {
const handleVisibilityChange = () => {
const isVisible = !document.hidden;
setFocused(isVisible);
if (isVisible) {
// App became visible, sync data
this.syncOnForeground();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
});
// Listen for online events
onlineManager.setEventListener((setOnline) => {
const handleOnline = () => {
setOnline(true);
this.syncOnReconnect();
};
const handleOffline = () => setOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
}
private async syncOnForeground() {
// Refetch critical data when app comes to foreground
await this.queryClient.refetchQueries({
type: 'active',
stale: true,
});
}
private async syncOnReconnect() {
// Resume paused mutations and refetch failed queries
await Promise.all([
this.queryClient.resumePausedMutations(),
this.queryClient.refetchQueries({
predicate: (query) => query.state.fetchStatus === 'paused',
}),
]);
}
// Background sync for mutations
syncMutation(mutationKey: string, data: unknown) {
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
type: 'BACKGROUND_SYNC',
mutationKey,
data,
});
}
}
}
// Usage
const backgroundSync = new BackgroundSyncManager(queryClient);Advanced integration with Page Lifecycle API for better resource management.
class PageLifecycleManager {
constructor(private queryClient: QueryClient) {
this.setupPageLifecycle();
}
private setupPageLifecycle() {
// Page Lifecycle API integration
if ('onfreeze' in document) {
document.addEventListener('freeze', this.handleFreeze.bind(this));
document.addEventListener('resume', this.handleResume.bind(this));
}
// Fallback for browsers without Page Lifecycle API
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
window.addEventListener('beforeunload', this.handleBeforeUnload.bind(this));
window.addEventListener('pagehide', this.handlePageHide.bind(this));
window.addEventListener('pageshow', this.handlePageShow.bind(this));
}
private handleFreeze() {
// Page is being frozen (backgrounded on mobile)
console.log('Page frozen - pausing queries');
// Cancel ongoing requests to save battery
this.queryClient.cancelQueries();
// Persist important cache data
const dehydratedState = dehydrate(this.queryClient);
try {
sessionStorage.setItem('query-cache-freeze', JSON.stringify(dehydratedState));
} catch (e) {
console.warn('Failed to persist cache on freeze');
}
}
private handleResume() {
// Page is being resumed
console.log('Page resumed - resuming queries');
// Restore cache if needed
try {
const stored = sessionStorage.getItem('query-cache-freeze');
if (stored) {
const state = JSON.parse(stored);
hydrate(this.queryClient, state);
sessionStorage.removeItem('query-cache-freeze');
}
} catch (e) {
console.warn('Failed to restore cache on resume');
}
// Refetch stale data
this.queryClient.refetchQueries({ stale: true });
}
private handleVisibilityChange() {
if (document.hidden) {
// Page hidden - reduce activity
this.reduceActivity();
} else {
// Page visible - resume normal activity
this.resumeActivity();
}
}
private handleBeforeUnload() {
// Page is about to unload - cleanup
this.queryClient.cancelQueries();
}
private handlePageHide(event: PageTransitionEvent) {
if (event.persisted) {
// Page is going into back/forward cache
this.handleFreeze();
}
}
private handlePageShow(event: PageTransitionEvent) {
if (event.persisted) {
// Page is coming back from back/forward cache
this.handleResume();
}
}
private reduceActivity() {
// Reduce query frequency when page is hidden
const queries = this.queryClient.getQueryCache().getAll();
queries.forEach(query => {
if (query.observers.length === 0) {
// Stop background refetching for inactive queries
query.destroy();
}
});
}
private resumeActivity() {
// Resume normal query activity when page is visible
this.queryClient.refetchQueries({
type: 'active',
predicate: (query) => {
// Only refetch if data is stale or hasn't been fetched recently
const staleTime = query.options.staleTime ?? 0;
return Date.now() - query.state.dataUpdatedAt > staleTime;
},
});
}
}
// Usage
const lifecycleManager = new PageLifecycleManager(queryClient);type SetupFn = (setEventState: (state?: boolean) => void) => (() => void) | void;
interface NotifyFunction {
(callback: () => void): void;
}
interface BatchNotifyFunction {
(callback: () => void): void;
}
interface ScheduleFunction {
(callback: () => void): void;
}Install with Tessl CLI
npx tessl i tessl/npm-tanstack--query-core