The framework agnostic core that powers TanStack Query for data fetching and caching
—
Core utility functions for key hashing, data manipulation, query matching, functional programming patterns, and advanced data management operations.
Functions for working with query and mutation keys.
/**
* Create a stable hash from a query or mutation key
* Used internally for cache key generation and comparison
* @param key - Query or mutation key to hash
* @returns Stable string hash
*/
function hashKey(key: QueryKey | MutationKey): string;
/**
* Check if two query keys match partially
* Used for flexible query matching in filters
* @param a - First query key
* @param b - Second query key
* @returns true if keys match partially
*/
function partialMatchKey(a: QueryKey, b: QueryKey): boolean;Usage Examples:
import { hashKey, partialMatchKey } from "@tanstack/query-core";
// Hash keys for comparison
const hash1 = hashKey(['user', 123]);
const hash2 = hashKey(['user', 123]);
console.log(hash1 === hash2); // true
// Partial key matching
const matches1 = partialMatchKey(['user'], ['user', 123]); // true
const matches2 = partialMatchKey(['user', 123], ['user']); // false
const matches3 = partialMatchKey(['user', 123], ['user', 123]); // true
// Use in custom filtering
const findUserQueries = (userId?: number) => {
const queries = queryClient.getQueryCache().getAll();
return queries.filter(query => {
if (userId) {
return partialMatchKey(['user', userId], query.queryKey);
}
return partialMatchKey(['user'], query.queryKey);
});
};Functions for matching queries and mutations against filters.
/**
* Check if a query matches the given filters
* Used internally by QueryClient methods for filtering operations
* @param filters - Query filters to match against
* @param query - Query instance to test
* @returns true if query matches all filters
*/
function matchQuery(filters: QueryFilters, query: Query): boolean;
/**
* Check if a mutation matches the given filters
* Used internally by QueryClient methods for filtering operations
* @param filters - Mutation filters to match against
* @param mutation - Mutation instance to test
* @returns true if mutation matches all filters
*/
function matchMutation(filters: MutationFilters, mutation: Mutation): boolean;Usage Examples:
import { matchQuery, matchMutation } from "@tanstack/query-core";
const queryCache = queryClient.getQueryCache();
const mutationCache = queryClient.getMutationCache();
// Custom query filtering
const customFilter = (query: Query) => {
return matchQuery({
queryKey: ['user'],
stale: true,
active: true,
}, query);
};
const matchingQueries = queryCache.getAll().filter(customFilter);
// Custom mutation filtering
const pendingUserMutations = mutationCache.getAll().filter(mutation => {
return matchMutation({
mutationKey: ['user'],
status: 'pending',
}, mutation);
});
// Advanced filtering logic
const complexQueryFilter = (query: Query) => {
// Match user queries that are either stale or have errors
const userMatch = matchQuery({ queryKey: ['user'] }, query);
const staleMatch = matchQuery({ stale: true }, query);
const errorMatch = matchQuery({ status: 'error' }, query);
return userMatch && (staleMatch || errorMatch);
};Functions for data transformation and structural sharing.
/**
* Replace data with structural sharing optimization
* Preserves object references when data hasn't changed
* @param a - Previous data
* @param b - New data
* @returns Data with optimal structural sharing
*/
function replaceEqualDeep<T>(a: unknown, b: T): T;
/**
* Special function for keeping previous data during updates
* Used with placeholderData to maintain UI state during refetches
* @param previousData - The previous data to keep
* @returns The same data or undefined
*/
function keepPreviousData<T>(previousData: T | undefined): T | undefined;Usage Examples:
import { replaceEqualDeep, keepPreviousData } from "@tanstack/query-core";
// Structural sharing optimization
const oldUserData = { id: 1, name: 'John', settings: { theme: 'dark' } };
const newUserData = { id: 1, name: 'John', settings: { theme: 'dark' } };
const optimizedData = replaceEqualDeep(oldUserData, newUserData);
// optimizedData.settings === oldUserData.settings (same reference)
// Keep previous data during refetch
const userQuery = new QueryObserver(queryClient, {
queryKey: ['user', userId],
queryFn: fetchUser,
placeholderData: keepPreviousData,
});
// Custom data transformation with structural sharing
const transformData = (oldData, newData) => {
const transformed = {
...newData,
computed: newData.value * 2,
timestamp: Date.now(),
};
return replaceEqualDeep(oldData, transformed);
};Special values for conditional behavior and skipping operations.
/**
* Special token used to skip query execution
* When used as queryFn, the query will not execute
*/
const skipToken: Symbol;
/**
* No-operation function
* Useful as a default or placeholder function
*/
function noop(): void;
/**
* Check if the current environment is server-side
* Useful for SSR-aware code
*/
const isServer: boolean;Usage Examples:
import { skipToken, noop, isServer } from "@tanstack/query-core";
// Conditional query execution
const userQuery = new QueryObserver(queryClient, {
queryKey: ['user', userId],
queryFn: userId ? fetchUser : skipToken, // Skip if no userId
});
// Skip query based on conditions
const conditionalQuery = new QueryObserver(queryClient, {
queryKey: ['data', id],
queryFn: shouldFetch ? fetchData : skipToken,
});
// No-op function usage
const defaultCallback = noop;
// Server-side detection
if (!isServer) {
// Client-side only code
setupBrowserEvents();
}
// SSR-aware query setup
const browserOnlyQuery = new QueryObserver(queryClient, {
queryKey: ['browser-data'],
queryFn: isServer ? skipToken : fetchBrowserData,
});Functions for error handling and decision making.
/**
* Determine if an error should be thrown based on configuration
* Used internally to decide whether to throw errors or return them in state
* @param throwError - Error throwing configuration
* @param params - Error and query parameters
* @returns true if error should be thrown
*/
function shouldThrowError<TError, TData>(
throwError: ThrowOnError<TData, TError> | undefined,
params: [TError, Query<TData, TError>]
): boolean;
/**
* Check if a value is a cancelled error
* Exported from the retryer module
* @param value - Value to check
* @returns true if value is a CancelledError
*/
function isCancelledError(value: any): value is CancelledError;
/**
* Error class for cancelled operations
* Thrown when queries or mutations are cancelled
* Exported from the retryer module
*/
class CancelledError extends Error {
constructor(options?: { revert?: boolean; silent?: boolean });
revert?: boolean;
silent?: boolean;
}Usage Examples:
import { shouldThrowError, CancelledError, isCancelledError } from "@tanstack/query-core";
// Custom error handling logic
const customThrowError = (error, query) => {
// Only throw errors for specific query types
if (query.queryKey[0] === 'critical-data') {
return true;
}
// Don't throw network errors
if (error.name === 'NetworkError') {
return false;
}
return shouldThrowError(true, [error, query]);
};
// Use in query observer
const observer = new QueryObserver(queryClient, {
queryKey: ['data'],
queryFn: fetchData,
throwOnError: customThrowError,
});
// Handle cancelled errors
try {
await queryClient.fetchQuery({
queryKey: ['data'],
queryFn: fetchData,
});
} catch (error) {
if (isCancelledError(error)) {
console.log('Query was cancelled:', error.message);
if (error.revert) {
console.log('Should revert optimistic updates');
}
if (!error.silent) {
console.log('Should show error message');
}
} else {
console.error('Query failed:', error);
}
}
// Manual cancellation
const cancelQuery = () => {
throw new CancelledError({
revert: true,
silent: false
});
};Type definitions for functional programming patterns and data updates.
/**
* Type for functional updates
* Can be either a new value or a function that transforms the old value
*/
type Updater<T> = T | ((old: T) => T);Usage Examples:
// The Updater type is exported and used throughout the API
type Updater<T> = T | ((old: T) => T);
// Usage in query data updates
queryClient.setQueryData(['user', 123], (oldData) => ({
...oldData,
lastSeen: new Date().toISOString(),
}));
// Custom functional update utility
const updateUserData = (updater: Updater<UserData>) => {
const currentData = queryClient.getQueryData(['user', userId]);
const newData = typeof updater === 'function'
? updater(currentData)
: updater;
queryClient.setQueryData(['user', userId], newData);
};
// Usage
updateUserData({ name: 'New Name' }); // Direct value
updateUserData(old => ({ ...old, age: old.age + 1 })); // Function updateUtilities for development and debugging scenarios.
// Environment detection
if (!isServer) {
// Enable development tools
if (process.env.NODE_ENV === 'development') {
// Development-only code
window.__QUERY_CLIENT__ = queryClient;
}
}
// Debug helper for query cache
const debugQueryCache = () => {
const queries = queryClient.getQueryCache().getAll();
console.table(queries.map(query => ({
key: JSON.stringify(query.queryKey),
status: query.state.status,
fetchStatus: query.state.fetchStatus,
dataUpdatedAt: new Date(query.state.dataUpdatedAt).toLocaleString(),
observers: query.observers.length,
isStale: query.isStale(),
isActive: query.isActive(),
})));
};
// Debug helper for mutation cache
const debugMutationCache = () => {
const mutations = queryClient.getMutationCache().getAll();
console.table(mutations.map(mutation => ({
id: mutation.mutationId,
key: JSON.stringify(mutation.options.mutationKey),
status: mutation.state.status,
submittedAt: new Date(mutation.state.submittedAt).toLocaleString(),
variables: JSON.stringify(mutation.state.variables),
})));
};
// Performance monitoring
const monitorQueryPerformance = () => {
const cache = queryClient.getQueryCache();
cache.subscribe((event) => {
if (event.type === 'updated' && event.action.type === 'success') {
const duration = event.action.dataUpdatedAt - event.action.fetchedAt;
console.log(`Query ${JSON.stringify(event.query.queryKey)} took ${duration}ms`);
}
});
};Complex utility patterns for advanced use cases.
// Query key factory pattern
const queryKeys = {
all: ['todos'] as const,
lists: () => [...queryKeys.all, 'list'] as const,
list: (filters: string) => [...queryKeys.lists(), { filters }] as const,
details: () => [...queryKeys.all, 'detail'] as const,
detail: (id: number) => [...queryKeys.details(), id] as const,
};
// Usage with utilities
const invalidateAllTodos = () => {
queryClient.invalidateQueries({ queryKey: queryKeys.all });
};
const invalidateListTodos = () => {
queryClient.invalidateQueries({ queryKey: queryKeys.lists() });
};
// Custom matcher utility
const createQueryMatcher = (baseKey: QueryKey) => {
return (query: Query): boolean => {
return partialMatchKey(baseKey, query.queryKey);
};
};
const todoMatcher = createQueryMatcher(['todos']);
const userMatcher = createQueryMatcher(['user']);
// Find queries using custom matchers
const todoQueries = queryClient.getQueryCache().getAll().filter(todoMatcher);
const userQueries = queryClient.getQueryCache().getAll().filter(userMatcher);
// Batch operations utility
const batchQueryOperations = (operations: Array<() => void>) => {
notifyManager.batch(() => {
operations.forEach(op => op());
});
};
// Usage
batchQueryOperations([
() => queryClient.setQueryData(['user', 1], userData1),
() => queryClient.setQueryData(['user', 2], userData2),
() => queryClient.invalidateQueries({ queryKey: ['posts'] }),
]);type QueryKey = ReadonlyArray<unknown>;
type MutationKey = ReadonlyArray<unknown>;
type Updater<T> = T | ((old: T) => T);
type ThrowOnError<TData, TError, TQueryData = TData, TQueryKey extends QueryKey = QueryKey> =
| boolean
| ((error: TError, query: Query<TQueryData, TError, TData, TQueryKey>) => boolean);
interface QueryFilters {
queryKey?: QueryKey;
exact?: boolean;
stale?: boolean;
predicate?: (query: Query) => boolean;
fetchStatus?: FetchStatus;
status?: QueryStatus;
type?: QueryTypeFilter;
}
interface MutationFilters {
mutationKey?: MutationKey;
exact?: boolean;
predicate?: (mutation: Mutation) => boolean;
status?: MutationStatus;
}
type QueryTypeFilter = 'all' | 'active' | 'inactive';
type FetchStatus = 'fetching' | 'paused' | 'idle';
type QueryStatus = 'pending' | 'error' | 'success';
type MutationStatus = 'idle' | 'pending' | 'success' | 'error';Install with Tessl CLI
npx tessl i tessl/npm-tanstack--query-core