CRP
HomeWhy CRPServicesOur TeamInsights
Book a Consultation
CRP
HomeWhy CRPServicesOur TeamInsightsBook a Consultation
Back to Insights
March 18, 2025

Data Fetching with TanStack Query

How TanStack Query handles caching, background refetching, and optimistic updates to make your UI feel instant.

What TanStack Query does

TanStack Query (formerly React Query) manages server state — data that lives on a remote server and needs to be fetched, cached, and kept in sync. It handles loading states, error states, background refetching, and cache invalidation so you don't have to.

In this stack, it's paired with tRPC so every procedure gets full query/mutation support with zero configuration.

useQuery

"use client";
 
import { useQuery } from "@tanstack/react-query";
import { useTRPC } from "@/services/trpc/client";
 
const PostList = () => {
  const trpc = useTRPC();
  const { data, isPending, isError } = useQuery(
    trpc.posts.list.queryOptions()
  );
 
  if (isPending) return <Skeleton className="h-40 w-full" />;
  if (isError) return <p className="text-destructive">Failed to load posts.</p>;
 
  return (
    <ul className="flex flex-col gap-4">
      {data.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

useMutation with cache invalidation

After a mutation succeeds, invalidate the relevant query so the list refreshes automatically:

const trpc = useTRPC();
const queryClient = useQueryClient();
 
const deletePost = useMutation(
  trpc.posts.delete.mutationOptions({
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: trpc.posts.list.queryKey() });
      toast.success("Post deleted");
    },
    onError: () => toast.error("Something went wrong"),
  })
);

Optimistic updates

For instant UI feedback before the server responds:

const toggleLike = useMutation(
  trpc.posts.toggleLike.mutationOptions({
    onMutate: async ({ postId }) => {
      await queryClient.cancelQueries({ queryKey: trpc.posts.list.queryKey() });
      const previous = queryClient.getQueryData(trpc.posts.list.queryKey());
      queryClient.setQueryData(trpc.posts.list.queryKey(), (old) =>
        old?.map((p) => p.id === postId ? { ...p, liked: !p.liked } : p)
      );
      return { previous };
    },
    onError: (_err, _vars, ctx) => {
      queryClient.setQueryData(trpc.posts.list.queryKey(), ctx?.previous);
    },
  })
);

Prefetching on the server

Prefetch queries in a Server Component and pass the dehydrated state to the client to avoid a loading flicker:

// app/posts/page.tsx (Server Component)
import { HydrationBoundary, dehydrate } from "@tanstack/react-query";
import { createQueryClient } from "@/services/trpc/query-client";
 
export default async function PostsPage() {
  const queryClient = createQueryClient();
  await queryClient.prefetchQuery(trpc.posts.list.queryOptions());
 
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <PostList />
    </HydrationBoundary>
  );
}

The client hydrates instantly with data already in the cache — no loading spinner on first paint.

Clearwater Revenue Partners

Hospitality revenue management consulting — outsourced strategy for independent and branded hotels across North America.

Quick Links

  • Home
  • Why CRP
  • Services
  • Our Team
  • Insights
  • Contact

Contact

  • hello@clearwaterrevenue.com
  • (720) 555-0198
  • Denver, Colorado

Connect

  • Rachel Moreno
  • Dana Kessler

© 2026 Clearwater Revenue Partners. All rights reserved.