CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-query

Hooks for managing, caching and syncing asynchronous and remote data in React

Pending
Overview
Eval results
Files

context.mddocs/

Context & Client Management

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.

Capabilities

QueryClientProvider Component

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

useQueryClient Hook

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

Default Context

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

QueryClient Class

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

Advanced Usage Patterns

Custom Client Configuration

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;

Multiple Client Isolation

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

Manual Cache Management

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

Context Sharing for Microfrontends

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/document

Error Boundaries Integration

Integrating 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

docs

context.md

error-handling.md

index.md

infinite-queries.md

mutations.md

parallel-queries.md

queries.md

ssr.md

status.md

tile.json