Hooks for managing, caching and syncing asynchronous and remote data in React
—
QueryClient provider system for sharing client instances across component trees with context isolation and configuration. This is the foundation that enables all React Query hooks to work.
The root provider component that makes QueryClient available to all child components.
/**
* Provides QueryClient instance to component tree
* @param props - Provider configuration with client and optional context settings
* @returns JSX element that wraps children with QueryClient context
*/
function QueryClientProvider(props: QueryClientProviderProps): JSX.Element;
type QueryClientProviderProps =
| QueryClientProviderPropsWithContext
| QueryClientProviderPropsWithContextSharing;
interface QueryClientProviderPropsWithContext {
/** QueryClient instance to provide */
client: QueryClient;
/** Child components that will have access to QueryClient */
children?: React.ReactNode;
/** Custom React context to use instead of default */
context?: React.Context<QueryClient | undefined>;
/** Context sharing must not be used with custom context */
contextSharing?: never;
}
interface QueryClientProviderPropsWithContextSharing {
/** QueryClient instance to provide */
client: QueryClient;
/** Child components that will have access to QueryClient */
children?: React.ReactNode;
/** Custom context cannot be used with context sharing */
context?: never;
/** Whether to share context across microfrontends/bundles */
contextSharing?: boolean;
}Usage Examples:
import { QueryClient, QueryClientProvider } from "react-query";
// Basic setup
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div className="App">
<Header />
<MainContent />
<Footer />
</div>
</QueryClientProvider>
);
}
// With configuration
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 2,
refetchOnWindowFocus: false
},
mutations: {
retry: 1
}
}
});
function AppWithDefaults() {
return (
<QueryClientProvider client={queryClient}>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Router>
</QueryClientProvider>
);
}
// Multiple contexts for different parts of app
const mainQueryClient = new QueryClient();
const adminQueryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 0 } // Always fresh for admin data
}
});
function AppWithMultipleContexts() {
return (
<QueryClientProvider client={mainQueryClient}>
<div>
<MainApp />
<QueryClientProvider client={adminQueryClient}>
<AdminPanel />
</QueryClientProvider>
</div>
</QueryClientProvider>
);
}Hook to access the QueryClient instance from context.
/**
* Access QueryClient instance from React context
* @param options - Optional context configuration
* @returns QueryClient instance
* @throws Error if no QueryClient is found in context
*/
function useQueryClient(options?: ContextOptions): QueryClient;
interface ContextOptions {
/** Custom React context to read from */
context?: React.Context<QueryClient | undefined>;
}Usage Examples:
import { useQueryClient } from "react-query";
// Basic usage
function MyComponent() {
const queryClient = useQueryClient();
const handleInvalidate = () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
};
const handleSetData = (newData: any) => {
queryClient.setQueryData(['user', 'current'], newData);
};
return (
<div>
<button onClick={handleInvalidate}>Refresh Posts</button>
<button onClick={() => handleSetData({ name: 'New Name' })}>
Update User
</button>
</div>
);
}
// With custom context
const AdminContext = React.createContext<QueryClient | undefined>(undefined);
function AdminComponent() {
const adminQueryClient = useQueryClient({ context: AdminContext });
const clearAdminCache = () => {
adminQueryClient.clear();
};
return (
<button onClick={clearAdminCache}>Clear Admin Cache</button>
);
}The default React context used by React Query.
/**
* Default React context for QueryClient
* Used automatically when no custom context is provided
*/
const defaultContext: React.Context<QueryClient | undefined>;The core client class that manages queries and mutations.
/**
* Central client for managing queries and mutations
* Handles caching, invalidation, and coordination
*/
class QueryClient {
constructor(options?: QueryClientConfig);
/** Get cached query data */
getQueryData<TData = unknown>(queryKey: QueryKey): TData | undefined;
/** Set query data in cache */
setQueryData<TData>(
queryKey: QueryKey,
data: TData | ((oldData: TData | undefined) => TData),
options?: SetDataOptions
): TData | undefined;
/** Get query state information */
getQueryState(queryKey: QueryKey): QueryState | undefined;
/** Invalidate queries to trigger refetch */
invalidateQueries(filters?: InvalidateQueryFilters): Promise<void>;
/** Refetch queries immediately */
refetchQueries(filters?: RefetchQueryFilters): Promise<QueryObserverResult[]>;
/** Fetch query data imperatively */
fetchQuery<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
options: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>
): Promise<TData>;
/** Fetch query data with separate parameters */
fetchQuery<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
queryKey: TQueryKey,
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
options?: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>
): Promise<TData>;
/** Prefetch query data without subscribing */
prefetchQuery<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
options: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>
): Promise<void>;
/** Prefetch query data with separate parameters */
prefetchQuery<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
queryKey: TQueryKey,
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
options?: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>
): Promise<void>;
/** Cancel ongoing queries */
cancelQueries(filters?: CancelQueryFilters): Promise<void>;
/** Remove queries from cache */
removeQueries(filters?: RemoveQueryFilters): void;
/** Clear all cached data */
clear(): void;
/** Execute mutation */
executeMutation<TData, TError, TVariables, TContext>(
options: MutationOptions<TData, TError, TVariables, TContext>
): Promise<TData>;
/** Check if queries are fetching */
isFetching(filters?: QueryFilters): number;
/** Check if mutations are pending */
isMutating(filters?: MutationFilters): number;
/** Get default query options */
defaultQueryOptions<T extends QueryOptions>(options?: T): T;
/** Get default mutation options */
defaultMutationOptions<T extends MutationOptions>(options?: T): T;
/** Mount client (called automatically by provider) */
mount(): void;
/** Unmount client (called automatically by provider) */
unmount(): void;
}
interface QueryClientConfig {
/** Default options for all queries */
queryCache?: QueryCache;
/** Default options for all mutations */
mutationCache?: MutationCache;
/** Default options for queries */
defaultOptions?: {
queries?: QueryOptions;
mutations?: MutationOptions;
};
/** Custom logger */
logger?: Logger;
}
interface FetchQueryOptions<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> extends QueryOptions<TQueryFnData, TError, TData, TQueryKey> {
queryKey: TQueryKey;
queryFn: QueryFunction<TQueryFnData, TQueryKey>;
}
interface InvalidateQueryFilters<TPageData = unknown> extends QueryFilters {
refetchType?: 'active' | 'inactive' | 'all' | 'none';
}
interface RefetchQueryFilters<TPageData = unknown> extends QueryFilters {
type?: 'active' | 'inactive' | 'all';
}
interface CancelQueryFilters extends QueryFilters {}
interface RemoveQueryFilters extends QueryFilters {}
interface SetDataOptions {
updatedAt?: number;
}Creating QueryClient with specific configurations:
import { QueryClient, QueryClientProvider } from "react-query";
// Production configuration
const prodQueryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: (failureCount, error) => {
// Don't retry 4xx errors
if (error.status >= 400 && error.status < 500) {
return false;
}
return failureCount < 3;
},
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
},
mutations: {
retry: 1,
onError: (error) => {
// Global error handling
console.error('Mutation failed:', error);
// Could trigger toast notification, analytics, etc.
}
}
}
});
// Development configuration
const devQueryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 0, // Always fresh in development
cacheTime: 0, // No caching in development
retry: false, // Don't retry in development
refetchOnWindowFocus: false
}
},
logger: {
log: console.log,
warn: console.warn,
error: console.error
}
});
const queryClient = process.env.NODE_ENV === 'production'
? prodQueryClient
: devQueryClient;Using separate QueryClients for different application areas:
// Separate clients for different data domains
const userDataClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 10 * 60 * 1000, // User data is relatively stable
cacheTime: 30 * 60 * 1000
}
}
});
const realTimeClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 0, // Real-time data should always be fresh
cacheTime: 5 * 60 * 1000,
refetchInterval: 30000 // Poll every 30 seconds
}
}
});
const analyticsClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
cacheTime: 60 * 60 * 1000, // Cache analytics for 1 hour
retry: 0 // Don't retry analytics queries
}
}
});
function App() {
return (
<QueryClientProvider client={userDataClient}>
<div>
<UserProfile />
<QueryClientProvider client={realTimeClient}>
<LiveDashboard />
</QueryClientProvider>
<QueryClientProvider client={analyticsClient}>
<AnalyticsPanel />
</QueryClientProvider>
</div>
</QueryClientProvider>
);
}Direct cache manipulation using QueryClient:
function CacheManager() {
const queryClient = useQueryClient();
const preloadUserData = async (userId: string) => {
// Preload user data
await queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 10 * 60 * 1000
});
};
const updateUserInCache = (userId: string, updates: Partial<User>) => {
// Update user data in multiple cache locations
queryClient.setQueryData(['user', userId], (oldUser: User) => ({
...oldUser,
...updates
}));
// Update user in lists
queryClient.setQueryData(['users'], (oldUsers: User[]) =>
oldUsers.map(user =>
user.id === userId ? { ...user, ...updates } : user
)
);
};
const invalidateRelatedData = (userId: string) => {
// Invalidate all user-related queries
queryClient.invalidateQueries({
queryKey: ['user', userId]
});
// Invalidate user posts
queryClient.invalidateQueries({
queryKey: ['posts'],
predicate: (query) => {
return query.queryKey.includes(userId);
}
});
};
const optimisticUpdate = (userId: string, newData: Partial<User>) => {
// Store current data for rollback
const previousUser = queryClient.getQueryData(['user', userId]);
// Apply optimistic update
queryClient.setQueryData(['user', userId], (old: User) => ({
...old,
...newData
}));
return () => {
// Rollback function
queryClient.setQueryData(['user', userId], previousUser);
};
};
return (
<div>
<button onClick={() => preloadUserData('123')}>
Preload User 123
</button>
<button onClick={() => updateUserInCache('123', { name: 'New Name' })}>
Update User 123 Name
</button>
<button onClick={() => invalidateRelatedData('123')}>
Refresh User 123 Data
</button>
</div>
);
}Sharing QueryClient across different React applications:
// In microfrontend setup
function MicrofrontendApp() {
const queryClient = new QueryClient();
return (
<QueryClientProvider
client={queryClient}
contextSharing={true} // Share context across bundles
>
<MyMicrofrontendContent />
</QueryClientProvider>
);
}
// This allows different React Query versions/bundles
// to share the same QueryClient instance when rendered
// in the same window/documentIntegrating QueryClient with React error boundaries:
class QueryErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Get QueryClient from context if needed
const queryClient = this.context;
// Clear potentially corrupted cache
queryClient?.clear();
// Log error
console.error('Query error boundary caught error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong.</h2>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
function AppWithErrorBoundary() {
return (
<QueryClientProvider client={queryClient}>
<QueryErrorBoundary>
<App />
</QueryErrorBoundary>
</QueryClientProvider>
);
}Install with Tessl CLI
npx tessl i tessl/npm-react-query