Irbisa · Cheatsheet
Data Fetching
Fetching in effects and its pitfalls, TanStack Query, caching and invalidation, Suspense for data, pagination, optimistic updates, errors
How do you load data from an API in a React component with
useEffect, and which states do you have to handle?type User = { id: string; name: string }; function Profile({ userId }: { userId: string }) { const [user, setUser] = useState<User | null>(null); const [error, setError] = useState<Error | null>(null);…What is TanStack Query, and what problems does it solve compared to fetching in
useEffect?import { useQuery } from '@tanstack/react-query'; type User = { id: number; name: string }; async function fetchUser(id: number): Promise<User> { const res = await fetch(`/api/users/${id}`);…Axios or
fetch: what are the practical differences, and why doesfetchnot reject on a 404 or 500 response?// fetch: resolves for any HTTP status, rejects only when no response arrivesasync function getUser(id: string): Promise<User> { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); // 404 and 500 end up here return (await res.json()) as User;}…What are the pitfalls of fetching data directly in
useEffect?How should you design query keys in TanStack Query, and why must every variable the query uses be in the key?
import { queryOptions } from '@tanstack/react-query'; type Filters = { status: 'open' | 'done'; page: number }; export const todoQueries = { all: () => ['todos'] as const,…Find the bug: clicking "Next page" changes the page number, but the list keeps showing page 1.
function Orders() { const [page, setPage] = useState(1); const { data } = useQuery({ queryKey: ['orders'], queryFn: () => fetchOrders(page), }); return ( <> <OrderList orders={data?.items ?? []} /> <button onClick={() => setPage((p) => p + 1)}>Next page ({page})</button> </> );}What is the difference between
staleTimeandgcTimein TanStack Query, and what are their defaults?import { QueryClient } from '@tanstack/react-query'; export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60_000, // fresh for a minute: no refetch on mount or focus…In TanStack Query v5, what is the difference between
isPending,isFetchingandisLoading, and when do you use each?When does TanStack Query refetch data automatically, and how do you control it?
How do you make one query wait for another (dependent queries) in TanStack Query?
import { skipToken, useQuery } from '@tanstack/react-query'; function Projects({ email }: { email: string }) { const { data: user } = useQuery({ queryKey: ['user', email], queryFn: () => getUserByEmail(email),…How do you update data with
useMutation, and how do you keep the cached lists in sync after it?import { useMutation, useQueryClient } from '@tanstack/react-query'; function AddTodo() { const queryClient = useQueryClient(); const addTodo = useMutation({ mutationFn: (title: string) => api.createTodo(title),…Find the bug: after a new todo is added, the list does not show it until the page is reloaded.
function ProjectTodos() { const { projectId = '' } = useParams(); // "42", a string from the URL const queryClient = useQueryClient(); const { data: todos = [] } = useQuery({ queryKey: ['projects', projectId, 'todos'], queryFn: () => api.getTodos(projectId), staleTime: 5 * 60_000, }); const addTodo = useMutation({ mutationFn: api.createTodo, // resolves with the saved todo, projectId: 42 onSuccess: (todo) => queryClient.invalidateQueries({ queryKey: ['projects', todo.projectId, 'todos'] }), }); return <TodoList todos={todos} onAdd={(title) => addTodo.mutate({ projectId, title })} />;}How do you implement an optimistic update with TanStack Query, including rollback on error?
type Todo = { id: number; title: string; done: boolean }; function useToggleTodo() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (todo: Todo) => api.updateTodo({ ...todo, done: !todo.done }),…How do you implement infinite scroll with TanStack Query?
import { useInfiniteQuery } from '@tanstack/react-query'; type Page = { items: Post[]; nextCursor: string | null }; function Feed() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({…How do you handle errors when fetching data in React: retries, error boundaries and errors in the background?
How do you cancel a request that is no longer needed, for example when the user leaves the page or types a new search?
function SearchResults({ query }: { query: string }) { const [results, setResults] = useState<Hit[]>([]); useEffect(() => { const controller = new AbortController(); fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })…How do you refresh an expired access token when several requests fail with 401 at the same time?
let refreshing: Promise<string> | null = null; function refreshAccessToken(): Promise<string> { // every 401 that arrives during a refresh waits for this same promise refreshing ??= fetch('/api/auth/refresh', { method: 'POST', credentials: 'include' }) .then(async (res) => {…How does Suspense for data fetching work in React 19, and how do you use it with TanStack Query?
import { Suspense } from 'react';import { useSuspenseQuery } from '@tanstack/react-query';import { ErrorBoundary } from 'react-error-boundary'; function UserName({ id }: { id: number }) { const { data } = useSuspenseQuery({…Find the bug: this component stays on the Suspense fallback forever and the network tab shows a new request on every render.
import { Suspense, use } from 'react'; function UserCard({ id }: { id: number }) { const user = use(fetch(`/api/users/${id}`).then((r) => r.json())); return <p>{user.name}</p>;} export function Page({ id }: { id: number }) { return ( <Suspense fallback={<p>Loading...</p>}> <UserCard id={id} /> </Suspense> );}What is a request waterfall in a React app, and how do you avoid it?
How do you prefetch data on the server and hydrate it into TanStack Query on the client (SSR, Next.js App Router)?
// app/posts/page.tsx (Server Component)import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query';import { postsQuery } from './queries';import { PostList } from './PostList'; // 'use client', calls useQuery(postsQuery) export default async function PostsPage() {…