TanStack Query v5: The New API and Why stale-while-revalidate Changes Everything

TanStack Query v5 unifies the hook API, adds queryOptions() for type-safe reuse, and makes stale-while-revalidate the default mental model for server state in React.

Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

May 3, 2026
7 min read
TanStack Query v5: The New API and Why stale-while-revalidate Changes Everything

What Is Stale-While-Revalidate?

One AI engineering post, weekly

LLM benchmarks, prompt techniques, and token-cost breakdowns — not another AI news roundup.

TanStack Query (formerly React Query) is built on a simple insight: server data in a UI is always potentially stale. Instead of blocking the user while you re-fetch, show the cached data immediately and update it in the background. This is stale-while-revalidate, and it makes your app feel instant.

v5 Unified API

v5 flattened the options API. Most commonly-used options are now top-level:

typescript
// v4
const { data } = useQuery(["users", id], () => fetchUser(id), {
  staleTime: 1000 * 60,
  keepPreviousData: true,
});

// v5  -  unified object, new option names
const { data } = useQuery({
  queryKey: ["users", id],
  queryFn: () => fetchUser(id),
  staleTime: 1000 * 60,
  placeholderData: "keepPrevious", // replaces keepPreviousData
});

Team workspace

Ship faster with chat, meetings, and projects in one place — Zlyqor.

Start free

queryOptions() for Type-Safe Reuse

Define queries once and reuse them in hooks and prefetch calls with full type inference:

typescript
// lib/queries/tasks.ts
import { queryOptions } from "@tanstack/react-query";

export const taskQueries = {
  list: (projectId: string) =>
    queryOptions({
      queryKey: ["tasks", projectId],
      queryFn: () => api.tasks.list(projectId),
      staleTime: 30_000,
    }),

  detail: (taskId: string) =>
    queryOptions({
      queryKey: ["tasks", "detail", taskId],
      queryFn: () => api.tasks.get(taskId),
    }),
};

// In a component
const { data: tasks } = useQuery(taskQueries.list(projectId));

// In a server component for prefetch
await queryClient.prefetchQuery(taskQueries.list(projectId));

// In a mutation's onSuccess
queryClient.invalidateQueries(taskQueries.list(projectId));

Optimistic Updates

typescript
const createTask = useMutation({
  mutationFn: (data: NewTask) => api.tasks.create(data),

  onMutate: async (newTask) => {
    // Cancel in-flight refetches
    await queryClient.cancelQueries(taskQueries.list(projectId));
    // Snapshot current data
    const previous = queryClient.getQueryData(taskQueries.list(projectId).queryKey);
    // Optimistically update
    queryClient.setQueryData(
      taskQueries.list(projectId).queryKey,
      (old: Task[]) => [...old, { ...newTask, id: "temp", status: "pending" }]
    );
    return { previous };
  },

  onError: (err, vars, context) => {
    // Roll back on error
    queryClient.setQueryData(
      taskQueries.list(projectId).queryKey,
      context?.previous
    );
  },

  onSettled: () => {
    queryClient.invalidateQueries(taskQueries.list(projectId));
  },
});

Suspense Mode

v5 stabilizes Suspense integration - no more experimental flags:

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

function TaskList({ projectId }: { projectId: string }) {
  // Throws a promise while loading  -  caught by parent Suspense
  const { data: tasks } = useSuspenseQuery(taskQueries.list(projectId));

  return <ul>{tasks.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
}

// Parent
<Suspense fallback={<Skeleton />}>
  <ErrorBoundary fallback={<Error />}>
    <TaskList projectId={id} />
  </ErrorBoundary>
</Suspense>

SSR With Next.js App Router

typescript
// app/projects/[id]/page.tsx
import { dehydrate, HydrationBoundary, QueryClient } from "@tanstack/react-query";
import { taskQueries } from "@/lib/queries/tasks";

export default async function ProjectPage({ params }: { params: { id: string } }) {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery(taskQueries.list(params.id));

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <TaskList projectId={params.id} />
    </HydrationBoundary>
  );
}

TanStack Query vs SWR

FeatureTanStack QuerySWR
Bundle size~13KB~4KB
MutationsFull APIManual
Optimistic updatesBuilt-inManual
DevtoolsFirst-classThird-party
Infinite queriesBuilt-inBuilt-in
SuspenseStableExperimental
Learning curveMediumLow

SWR is great for simple fetching. TanStack Query wins when you need mutations, optimistic updates, or complex cache management.

References: TanStack Query · GitHub · v5 migration

#tanstack-query#react-query#data-fetching#caching#v5

// discussion

Comments

0/4000
Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

Visionary technologist, software engineer, and machine learning specialist. Founder and CEO of Pristren, directing engineering teams that ship production-grade AI/ML pipelines, mission-critical full-stack applications, and developer tooling. Creator of Zlyqor, the unified team workspace platform. Author of 540+ technical guides and benchmark research reports on large language models, agentic workflows, Model Context Protocol (MCP), and modern web stacks.

PristrenZlyqor