Skip to content
>_devvkit
$devvkit learn --librarie tanstack-query-(react-query)-guide

TanStack Query (React Query) Guide

[react][data-fetching][caching]
JavaScript / TypeScript
Install
npm install @tanstack/react-query

TanStack Query solves keeping server state in sync with the UI. It handles caching, background refetching, and optimistic updates.

Server state is fundamentally different from client state. TanStack Query treats server state as a cache, not source of truth.

v5 uses useQuery for reads, useMutation for writes, and queryClient.invalidateQueries to refetch.

Setup

ProviderWrap app with QueryClientProvider.
const queryClient = new QueryClient();
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>

Queries

Basic queryFetch data.
const { data, isLoading } = useQuery({
  queryKey: ['users'],
  queryFn: () => fetch('/api/users').then(r => r.json()),
});
Query with paramsDepend on variable.
const { data } = useQuery({
  queryKey: ['users', userId],
  queryFn: () => fetch('/api/users/' + userId).then(r => r.json()),
});
EnabledSkip until condition.
useQuery({ queryKey: ['users', userId], queryFn: fetchUser, enabled: !!userId });

Mutations

MutationCreate and invalidate.
const mutation = useMutation({
  mutationFn: (newUser) => fetch('/api/users', { method: 'POST', body: JSON.stringify(newUser) }),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
});
Optimistic updateUpdate UI before server.
useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    const prev = queryClient.getQueryData(['todos']);
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
    return { prev };
  },
  onError: (err, newTodo, context) => queryClient.setQueryData(['todos'], context.prev),
  onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
});

Caching

Stale timeControl freshness duration.
useQuery({ queryKey: ['users'], queryFn: fetchUsers, staleTime: 5 * 60 * 1000, gcTime: 30 * 60 * 1000 });

Pagination

Infinite queryLoad more pattern.
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: ({ pageParam }) => fetch(/api/projects?cursor=).then(r => r.json()),
  initialPageParam: 0,
  getNextPageParam: (lastPage) => lastPage.nextCursor ?? null,
});