The framework agnostic core that powers TanStack Query for data fetching and caching
—
Reactive observers for tracking query state changes with automatic updates, optimistic results, lifecycle management, and intelligent subscription handling.
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();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();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;
}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;
}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