TanStack Query (React Query)
Expert assistance with TanStack Query - Powerful data fetching for React.
Overview
TanStack Query manages server state in React:
-
Caching: Automatic caching and background updates
-
Refetching: Smart refetch strategies
-
Mutations: Optimistic updates and cache invalidation
-
DevTools: Built-in development tools
-
TypeScript: Full TypeScript support
Installation
npm install @tanstack/react-query npm install --save-dev @tanstack/react-query-devtools
Setup
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, // 1 minute cacheTime: 5 * 60 * 1000, // 5 minutes refetchOnWindowFocus: false, }, }, });
function App() { return ( <QueryClientProvider client={queryClient}> <YourApp /> <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ); }
useQuery
import { useQuery } from '@tanstack/react-query';
function CertificateList() { const { data, isLoading, error } = useQuery({ queryKey: ['certificates'], queryFn: () => fetch('/api/certificates').then(res => res.json()), });
if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>;
return ( <ul> {data.map(cert => ( <li key={cert.id}>{cert.subject}</li> ))} </ul> ); }
Query with Parameters
function CertificateDetail({ id }: { id: string }) {
const { data: certificate } = useQuery({
queryKey: ['certificate', id],
queryFn: () => fetch(/api/certificates/${id}).then(res => res.json()),
});
return <div>{certificate?.subject}</div>; }
Dependent Queries
function Certificate({ id }: { id: string }) { const { data: certificate } = useQuery({ queryKey: ['certificate', id], queryFn: () => fetchCertificate(id), });
// Only run if certificate exists const { data: ca } = useQuery({ queryKey: ['ca', certificate?.caId], queryFn: () => fetchCA(certificate.caId), enabled: !!certificate?.caId, });
return <div>{ca?.subject}</div>; }
useMutation
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateCertificate() { const queryClient = useQueryClient();
const mutation = useMutation({ mutationFn: (newCert) => { return fetch('/api/certificates', { method: 'POST', body: JSON.stringify(newCert), }); }, onSuccess: () => { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: ['certificates'] }); }, });
return ( <button onClick={() => mutation.mutate({ subject: 'CN=example.com' })} disabled={mutation.isPending} > {mutation.isPending ? 'Creating...' : 'Create Certificate'} </button> ); }
Optimistic Updates
const mutation = useMutation({ mutationFn: updateCertificate, onMutate: async (newCert) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['certificates'] });
// Snapshot previous value
const previousCerts = queryClient.getQueryData(['certificates']);
// Optimistically update
queryClient.setQueryData(['certificates'], (old) =>
old.map((cert) =>
cert.id === newCert.id ? newCert : cert
)
);
return { previousCerts };
}, onError: (err, newCert, context) => { // Rollback on error queryClient.setQueryData(['certificates'], context.previousCerts); }, onSettled: () => { // Refetch after success or error queryClient.invalidateQueries({ queryKey: ['certificates'] }); }, });
Pagination
function CertificateList() { const [page, setPage] = useState(1);
const { data, isLoading } = useQuery({ queryKey: ['certificates', page], queryFn: () => fetchCertificates(page), keepPreviousData: true, // Keep old data while fetching new });
return ( <> <ul> {data?.certificates.map(cert => ( <li key={cert.id}>{cert.subject}</li> ))} </ul>
<button onClick={() => setPage(p => p - 1)} disabled={page === 1}>
Previous
</button>
<button onClick={() => setPage(p => p + 1)} disabled={!data?.hasMore}>
Next
</button>
</>
); }
Infinite Queries
import { useInfiniteQuery } from '@tanstack/react-query';
function InfiniteCertificates() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, } = useInfiniteQuery({ queryKey: ['certificates'], queryFn: ({ pageParam = 1 }) => fetchCertificates(pageParam), getNextPageParam: (lastPage, pages) => lastPage.nextCursor, initialPageParam: 1, });
return ( <> {data?.pages.map((page, i) => ( <div key={i}> {page.certificates.map(cert => ( <div key={cert.id}>{cert.subject}</div> ))} </div> ))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
</>
); }
Cache Invalidation
const queryClient = useQueryClient();
// Invalidate all queries queryClient.invalidateQueries();
// Invalidate specific query queryClient.invalidateQueries({ queryKey: ['certificates'] });
// Invalidate query with params queryClient.invalidateQueries({ queryKey: ['certificate', '123'] });
// Invalidate all queries starting with key queryClient.invalidateQueries({ queryKey: ['certificates'], exact: false });
// Remove query from cache queryClient.removeQueries({ queryKey: ['certificates'] });
// Reset query to initial state queryClient.resetQueries({ queryKey: ['certificates'] });
Manual Cache Updates
// Set query data queryClient.setQueryData(['certificate', '123'], newCertificate);
// Update query data queryClient.setQueryData(['certificates'], (old) => old.map((cert) => cert.id === '123' ? updated : cert) );
// Get query data const certificates = queryClient.getQueryData(['certificates']);
// Prefetch query await queryClient.prefetchQuery({ queryKey: ['certificate', '123'], queryFn: () => fetchCertificate('123'), });
Best Practices
-
Query Keys: Use arrays for structured keys ['certificates', { status: 'active' }]
-
Stale Time: Set appropriate stale time for your data
-
Cache Time: Keep data in cache longer than stale time
-
Optimistic Updates: Improve UX with optimistic updates
-
Error Handling: Handle errors gracefully
-
Invalidation: Invalidate related queries on mutations
-
Pagination: Use keepPreviousData for better UX
-
DevTools: Use DevTools for debugging
-
TypeScript: Define types for query data
-
Prefetching: Prefetch data for better performance
Resources
-
Documentation: https://tanstack.com/query