CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--query-core

The framework agnostic core that powers TanStack Query for data fetching and caching

Pending
Overview
Eval results
Files

browser-integration.mddocs/

Browser Integration

Browser event management for automatic refetching on focus and network reconnection with customizable event handling, background sync, and visibility-based optimizations.

Capabilities

Focus Manager

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);
  };
});

Online Manager

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);
  };
});

Notification Manager

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();
  }
});

Background Sync Integration

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);

Page Lifecycle Integration

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);

Core Types

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

docs

browser-integration.md

cache-management.md

client-management.md

hydration.md

index.md

infinite-queries.md

mutations.md

query-observers.md

query-operations.md

utilities.md

tile.json