Teardown

Queries

Standard queries, infinite queries, and cache operations.

Standard Queries

Use the query() method to create type-safe query configurations:

class ProjectsClient extends QueryClient<"projects"> {
  getProjects(userId: string) {
    return this.query({
      key: ["list", userId],
      fn: async () => {
        const response = await fetch(`/api/projects?userId=${userId}`);
        return response.json();
      },
      staleTime: 2 * 60 * 1000, // Override default
    });
  }

  getProject(projectId: string) {
    return this.query({
      key: ["byId", projectId],
      enabled: projectId !== "",
      fn: async () => {
        const response = await fetch(`/api/projects/${projectId}`);
        return response.json();
      },
    });
  }
}

Using in Components

import { useQuery } from "@tanstack/react-query";

function ProjectsList({ userId }) {
  const projectsClient = useProjectsClient();
  const { data, isLoading, error } = useQuery(projectsClient.getProjects(userId));

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {data?.map(project => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  );
}

Cache Operations

Each query result includes utility methods for cache management:

const projectsQuery = projectsClient.getProjects(userId);

// One-time fetch (doesn't subscribe to updates)
const data = await projectsQuery.fetch();

// Populate cache proactively
await projectsQuery.prefetch();

// Read current cached value synchronously
const cached = projectsQuery.get();

// Update cache directly
projectsQuery.set(newData);

// Invalidate and trigger refetch
await projectsQuery.refresh();

Infinite Queries

For paginated or infinite-scroll data, use infiniteQuery():

class ProjectsClient extends QueryClient<"projects"> {
  getProjectsInfinite() {
    return this.infiniteQuery({
      key: ["list", "infinite"],
      fn: async ({ pageParam }) => {
        const response = await fetch(`/api/projects?page=${pageParam}`);
        return response.json();
      },
      initialPageParam: 1,
      getNextPageParam: (lastPage, _allPages, lastPageParam) => {
        return lastPage.hasMore ? lastPageParam + 1 : undefined;
      },
    });
  }
}

Using Infinite Queries

import { useInfiniteQuery } from "@tanstack/react-query";

function InfiniteProjectsList() {
  const projectsClient = useProjectsClient();
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = 
    useInfiniteQuery(projectsClient.getProjectsInfinite());

  return (
    <div>
      {data?.pages.map(page =>
        page.projects.map(project => (
          <div key={project.id}>{project.name}</div>
        ))
      )}
      
      {hasNextPage && (
        <button 
          onClick={() => fetchNextPage()} 
          disabled={isFetchingNextPage}
        >
          {isFetchingNextPage ? "Loading..." : "Load More"}
        </button>
      )}
    </div>
  );
}

Infinite Query Result

interface CreateInfiniteQueryResult {
  queryKey: QueryKey;
  // ... other InfiniteQueryOptions

  fetch(): Promise<InfiniteData<TQueryFnData, TPageParam>>;
  refresh(): Promise<void>;
}

Query Options

All standard TanStack Query options are supported:

this.query({
  key: ["list", userId],
  fn: fetchProjects,
  
  // Common options
  enabled: userId !== "",           // Conditional fetching
  staleTime: 5 * 60 * 1000,        // Fresh for 5 minutes
  gcTime: 30 * 60 * 1000,          // Keep unused 30 minutes
  refetchOnWindowFocus: true,       // Refetch on focus
  refetchOnReconnect: true,         // Refetch on network reconnect
  retry: 3,                         // Retry failed requests
  retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
});

Conditional Queries

Use enabled to conditionally run queries:

getProject(projectId: string) {
  return this.query({
    key: ["byId", projectId],
    enabled: projectId !== "",  // Only fetch when ID is provided
    fn: async () => {
      const response = await fetch(`/api/projects/${projectId}`);
      return response.json();
    },
  });
}

Query Dependencies

Chain dependent queries by using data from one as args for another:

function ProjectDetails({ projectId }) {
  const queryClients = useQueryClients();
  
  // First query
  const { data: project } = useQuery(
    queryClients.projects.getProject(projectId)
  );
  
  // Dependent query - only runs when project exists
  const { data: owner } = useQuery({
    ...queryClients.users.getUser(project?.ownerId ?? ""),
    enabled: !!project?.ownerId,
  });

  return (
    <div>
      <h1>{project?.name}</h1>
      <p>Owner: {owner?.name}</p>
    </div>
  );
}