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

query-observers.mddocs/

Query Observers

Reactive observers for tracking query state changes with automatic updates, optimistic results, lifecycle management, and intelligent subscription handling.

Capabilities

QueryObserver

The primary observer class for tracking individual query state changes.

/**
 * Observer for tracking query state changes and managing subscriptions
 * Provides reactive updates when query data, loading state, or errors change
 */
class QueryObserver<TData = unknown, TError = Error, TQueryData = TData, TQueryKey extends QueryKey = QueryKey> {
  constructor(client: QueryClient, options: QueryObserverOptions<TData, TError, TQueryData, TQueryKey>);
  
  /**
   * Get the current result snapshot
   * Returns the latest query state without subscribing
   * @returns Current query observer result
   */
  getCurrentResult(): QueryObserverResult<TData, TError>;
  
  /**
   * Get the current query instance
   * @returns The underlying Query instance
   */
  getCurrentQuery(): Query<TQueryData, TError, TData, TQueryKey>;
  
  /**
   * Subscribe to query state changes
   * @param onStoreChange - Callback function called when state changes
   * @returns Unsubscribe function
   */
  subscribe(onStoreChange: (result: QueryObserverResult<TData, TError>) => void): () => void;
  
  /**
   * Update observer options
   * @param options - New observer options to merge
   */
  setOptions(options: QueryObserverOptions<TData, TError, TQueryData, TQueryKey>): void;
  
  /**
   * Get optimistic result for given options
   * Useful for getting result before actually changing options
   * @param options - Options to get optimistic result for
   * @returns Optimistic query observer result
   */
  getOptimisticResult(options: QueryObserverOptions<TData, TError, TQueryData, TQueryKey>): QueryObserverResult<TData, TError>;
  
  /**
   * Trigger a refetch of the query
   * @param options - Refetch options
   * @returns Promise resolving to the new result
   */
  refetch(options?: RefetchOptions): Promise<QueryObserverResult<TData, TError>>;
  
  /**
   * Fetch with optimistic options
   * @param options - Options for optimistic fetch
   * @returns Promise resolving to the result
   */
  fetchOptimistic(options: QueryObserverOptions<TData, TError, TQueryData, TQueryKey>): Promise<QueryObserverResult<TData, TError>>;
  
  /**
   * Destroy the observer and cleanup subscriptions
   */
  destroy(): void;
  
  /**
   * Track specific properties of the result for selective updates
   * @param result - Result to track
   * @param onPropTracked - Callback when property is tracked
   * @returns Tracked result
   */
  trackResult(result: QueryObserverResult<TData, TError>, onPropTracked?: () => void): QueryObserverResult<TData, TError>;
  
  /**
   * Check if should refetch on window focus
   * @returns true if should refetch on focus
   */
  shouldFetchOnWindowFocus(): boolean;
  
  /**
   * Check if should refetch on reconnect
   * @returns true if should refetch on reconnect
   */
  shouldFetchOnReconnect(): boolean;
}

interface QueryObserverOptions<TData = unknown, TError = Error, TQueryData = TData, TQueryKey extends QueryKey = QueryKey> {
  queryKey: TQueryKey;
  queryFn?: QueryFunction<TQueryData, TQueryKey>;
  enabled?: boolean | ((query: Query<TQueryData, TError, TData, TQueryKey>) => boolean);
  networkMode?: NetworkMode;
  initialData?: TData | InitialDataFunction<TData>;
  initialDataUpdatedAt?: number | (() => number | undefined);
  placeholderData?: TData | PlaceholderDataFunction<TData>;
  staleTime?: number | ((query: Query<TQueryData, TError, TData, TQueryKey>) => number);
  gcTime?: number;
  refetchInterval?: number | false | ((data: TData | undefined, query: Query<TQueryData, TError, TData, TQueryKey>) => number | false);
  refetchIntervalInBackground?: boolean;
  refetchOnMount?: boolean | "always" | ((query: Query<TQueryData, TError, TData, TQueryKey>) => boolean | "always");
  refetchOnWindowFocus?: boolean | "always" | ((query: Query<TQueryData, TError, TData, TQueryKey>) => boolean | "always");
  refetchOnReconnect?: boolean | "always" | ((query: Query<TQueryData, TError, TData, TQueryKey>) => boolean | "always");
  retry?: RetryValue<TError>;
  retryDelay?: RetryDelayValue<TError>;
  select?: (data: TQueryData) => TData;
  throwOnError?: ThrowOnError<TQueryData, TError, TData, TQueryKey>;
  meta?: QueryMeta;
  structuralSharing?: boolean | ((oldData: TData | undefined, newData: TData) => TData);
}

interface QueryObserverResult<TData = unknown, TError = Error> {
  data: TData | undefined;
  dataUpdatedAt: number;
  error: TError | null;
  errorUpdatedAt: number;
  failureCount: number;
  failureReason: TError | null;
  fetchStatus: FetchStatus;
  isError: boolean;
  isFetched: boolean;
  isFetchedAfterMount: boolean;
  isFetching: boolean;
  isInitialLoading: boolean;
  isLoading: boolean;
  isLoadingError: boolean;
  isPaused: boolean;
  isPlaceholderData: boolean;
  isPending: boolean;
  isRefetching: boolean;
  isRefetchError: boolean;
  isStale: boolean;
  isSuccess: boolean;
  refetch: (options?: RefetchOptions) => Promise<QueryObserverResult<TData, TError>>;
  status: QueryStatus;
}

Usage Examples:

import { QueryClient, QueryObserver } from "@tanstack/query-core";

const queryClient = new QueryClient();

// Create observer
const observer = new QueryObserver(queryClient, {
  queryKey: ['user', 123],
  queryFn: async () => {
    const response = await fetch('/api/user/123');
    return response.json();
  },
  staleTime: 5 * 60 * 1000, // 5 minutes
  refetchOnWindowFocus: true,
});

// Subscribe to changes
const unsubscribe = observer.subscribe((result) => {
  console.log('Data:', result.data);
  console.log('Loading:', result.isLoading);
  console.log('Error:', result.error);
  console.log('Status:', result.status);
  
  if (result.isSuccess) {
    console.log('User loaded successfully:', result.data);
  }
  
  if (result.isError) {
    console.error('Failed to load user:', result.error);
  }
});

// Get current result without subscribing
const currentResult = observer.getCurrentResult();

// Update options
observer.setOptions({
  queryKey: ['user', 123],
  queryFn: async () => {
    const response = await fetch('/api/user/123?v=2');
    return response.json();
  },
  staleTime: 10 * 60 * 1000, // 10 minutes
});

// Manual refetch
const newResult = await observer.refetch();

// Cleanup
unsubscribe();
observer.destroy();

QueriesObserver

Observer for tracking multiple queries simultaneously.

/**
 * Observer for tracking multiple queries simultaneously
 * Provides combined results and manages multiple query subscriptions
 */
class QueriesObserver<TCombinedResult = QueryObserverResult[]> {
  constructor(
    client: QueryClient,
    queries: QueryObserverOptionsForCreateQueries[],
    options?: QueriesObserverOptions<TCombinedResult>
  );
  
  /**
   * Update the queries being observed
   * @param queries - New array of query options
   * @param options - Observer options
   */
  setQueries(
    queries: QueryObserverOptionsForCreateQueries[],
    options?: QueriesObserverOptions<TCombinedResult>
  ): void;
  
  /**
   * Get current combined result from all queries
   * @returns Combined result from all observed queries
   */
  getCurrentResult(): TCombinedResult;
  
  /**
   * Get optimistic combined result for given queries
   * @param queries - Query options to get optimistic result for
   * @param options - Observer options
   * @returns Optimistic combined result
   */
  getOptimisticResult(
    queries: QueryObserverOptionsForCreateQueries[],
    options?: QueriesObserverOptions<TCombinedResult>
  ): TCombinedResult;
  
  /**
   * Subscribe to changes in any of the observed queries
   * @param onStoreChange - Callback called when any query changes
   * @returns Unsubscribe function
   */
  subscribe(onStoreChange: (result: TCombinedResult) => void): () => void;
  
  /**
   * Destroy the observer and cleanup all subscriptions
   */
  destroy(): void;
}

interface QueriesObserverOptions<TCombinedResult> {
  combine?: (results: QueryObserverResult[]) => TCombinedResult;
}

type QueryObserverOptionsForCreateQueries = QueryObserverOptions & {
  queryKey: QueryKey;
};

Usage Examples:

import { QueryClient, QueriesObserver } from "@tanstack/query-core";

const queryClient = new QueryClient();

// Create queries observer
const queriesObserver = new QueriesObserver(queryClient, [
  {
    queryKey: ['user', 123],
    queryFn: async () => {
      const response = await fetch('/api/user/123');
      return response.json();
    },
  },
  {
    queryKey: ['posts', 123],
    queryFn: async () => {
      const response = await fetch('/api/user/123/posts');
      return response.json();
    },
  },
  {
    queryKey: ['settings', 123],
    queryFn: async () => {
      const response = await fetch('/api/user/123/settings');
      return response.json();
    },
  },
], {
  combine: (results) => ({
    user: results[0],
    posts: results[1],
    settings: results[2],
    isLoading: results.some(result => result.isLoading),
    hasError: results.some(result => result.isError),
  }),
});

// Subscribe to combined changes
const unsubscribe = queriesObserver.subscribe((result) => {
  console.log('User data:', result.user.data);
  console.log('Posts data:', result.posts.data);
  console.log('Settings data:', result.settings.data);
  console.log('Any loading:', result.isLoading);
  console.log('Any error:', result.hasError);
});

// Update queries
queriesObserver.setQueries([
  {
    queryKey: ['user', 456], // Different user
    queryFn: async () => {
      const response = await fetch('/api/user/456');
      return response.json();
    },
  },
  {
    queryKey: ['posts', 456],
    queryFn: async () => {
      const response = await fetch('/api/user/456/posts');
      return response.json();
    },
  },
]);

// Cleanup
unsubscribe();
queriesObserver.destroy();

Result Properties

Understanding the properties available in query observer results.

interface QueryObserverResult<TData = unknown, TError = Error> {
  /** The last successfully resolved data for the query */
  data: TData | undefined;
  
  /** The timestamp for when the query was most recently returned in a resolved state */
  dataUpdatedAt: number;
  
  /** The error object for the query, if an error was thrown */
  error: TError | null;
  
  /** The timestamp for when the query most recently returned the error status */
  errorUpdatedAt: number;
  
  /** The failure count for the query */
  failureCount: number;
  
  /** The failure reason for the query retry */
  failureReason: TError | null;
  
  /** The fetch status of the query */
  fetchStatus: FetchStatus;
  
  /** Will be true if the query has been fetched */
  isFetched: boolean;
  
  /** Will be true if the query has been fetched after the component mounted */
  isFetchedAfterMount: boolean;
  
  /** Will be true if the query is currently fetching */
  isFetching: boolean;
  
  /** Will be true if the query failed while fetching for the first time */
  isLoadingError: boolean;
  
  /** Will be true if the query is currently paused */
  isPaused: boolean;
  
  /** Will be true if the data shown is placeholder data */
  isPlaceholderData: boolean;
  
  /** Will be true if the query is currently refetching */
  isRefetching: boolean;
  
  /** Will be true if the query failed while refetching */
  isRefetchError: boolean;
  
  /** Will be true if the query data is stale */
  isStale: boolean;
  
  /** Derived booleans from status */
  isError: boolean;
  isInitialLoading: boolean;
  isLoading: boolean;
  isPending: boolean;
  isSuccess: boolean;
  
  /** Function to manually refetch the query */
  refetch: (options?: RefetchOptions) => Promise<QueryObserverResult<TData, TError>>;
  
  /** The status of the query */
  status: QueryStatus;
}

Observer Configuration

Advanced configuration options for query observers.

type PlaceholderDataFunction<T> = (previousValue: T | undefined, previousQuery: Query | undefined) => T;

type ThrowOnError<TQueryFnData, TError, TData, TQueryKey extends QueryKey> = 
  | boolean 
  | ((error: TError, query: Query<TQueryFnData, TError, TData, TQueryKey>) => boolean);

interface RefetchOptions extends CancelOptions {
  throwOnError?: boolean;
}

interface CancelOptions {
  revert?: boolean;
  silent?: boolean;
}

Core Types

type QueryStatus = 'pending' | 'error' | 'success';
type FetchStatus = 'fetching' | 'paused' | 'idle';
type NetworkMode = 'online' | 'always' | 'offlineFirst';

type RetryValue<TError> = boolean | number | ((failureCount: number, error: TError) => boolean);
type RetryDelayValue<TError> = number | ((failureCount: number, error: TError, query: Query) => number);

type InitialDataFunction<T> = () => T | undefined;

interface QueryMeta extends Record<string, unknown> {}

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