iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

TanStack Query

TanStack Query (formerly React Query): server state for React. Caching, refetching, mutations, and the patterns that replace useEffect for data.

React — TanStack Query

EXAMPLE
// Install: npm install @tanstack/react-query
import { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// ===== Setup =====
const qc = new QueryClient({
  defaultOptions: { queries: { staleTime: 60_000, retry: 1 } },
});

function App() {
  return <QueryClientProvider client={qc}><Routes /></QueryClientProvider>;
}

// ===== Query =====
function UserList() {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['users'],
    queryFn: async () => {
      const r = await fetch('/api/users');
      if (!r.ok) throw new Error('failed');
      return r.json();
    },
    staleTime: 60_000,
  });
  if (isLoading) return <p>Loading</p>;
  if (error) return <p>Error</p>;
  return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

// ===== Query keys =====
useQuery({ queryKey: ['user', userId] });        // per-id cache
useQuery({ queryKey: ['posts', { tag, page }] }); // structured key

// ===== Mutation =====
function CreatePost() {
  const qc = useQueryClient();
  const { mutate, isPending } = useMutation({
    mutationFn: (newPost) => fetch('/api/posts', { method: 'POST', body: JSON.stringify(newPost) }).then(r => r.json()),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['posts'] }),
  });
  return <button disabled={isPending} onClick={() => mutate({ title: 'hi' })}>Create</button>;
}

// ===== Optimistic update =====
const { mutate } = useMutation({
  mutationFn: updatePost,
  onMutate: async (newPost) => {
    await qc.cancelQueries({ queryKey: ['posts'] });
    const prev = qc.getQueryData(['posts']);
    qc.setQueryData(['posts'], (old) => old.map(p => p.id === newPost.id ? newPost : p));
    return { prev };
  },
  onError: (err, newPost, ctx) => qc.setQueryData(['posts'], ctx.prev),
  onSettled: () => qc.invalidateQueries({ queryKey: ['posts'] }),
});

// ===== Infinite query =====
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: ({ pageParam = 0 }) => fetch(\`/api/posts?cursor=${pageParam}\`).then(r => r.json()),
  getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
  initialPageParam: 0,
});

// ===== Devtools =====
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
<ReactQueryDevtools initialIsOpen={false} />

// ===== Patterns =====
// - Stop writing useEffect for fetches
// - Use queryKey arrays for cache scoping
// - Invalidate after mutations to refetch dependents
// - staleTime > 0 to dedupe identical queries

// ===== Pitfalls =====
// - Different queryKey per render -> new cache entries each render
// - Mutation onSuccess without invalidate -> stale UI
// - Using as global state replacement -> use Zustand / Context for client state

Why it matters

TanStack Query is the standard for server state in React. Replace useEffect-fetches with useQuery, cache by queryKey, mutate + invalidate. Add optimistic updates and infinite queries when needed. The mental shift: server state is its own thing, not generic state.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { useQuery } from '@tanstack/react-query';
const { data, isLoading } = useQuery({
    queryKey: ['users'], queryFn: () => fetch('/api/users').then(r => r.json()),
});
Try it Yourself »

Discussion

Loading…