Irbisa · Шпаргалка
Загрузка данных
Запросы в эффектах и их ловушки, TanStack Query, кэш и инвалидация, Suspense для данных, пагинация, оптимистичные обновления, ошибки
Как загрузить данные из API в React-компоненте через
useEffectи какие состояния нужно обработать?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);…Что такое TanStack Query и какие проблемы он решает по сравнению с загрузкой в
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 или
fetch: в чём практическая разница и почемуfetchне отклоняет промис при ответе 404 или 500?// 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;}…Какие подводные камни у загрузки данных прямо в
useEffect?Как проектировать query keys в TanStack Query и почему каждая переменная, которую использует запрос, должна быть в ключе?
import { queryOptions } from '@tanstack/react-query'; type Filters = { status: 'open' | 'done'; page: number }; export const todoQueries = { all: () => ['todos'] as const,…Найдите баг: по нажатию «Next page» номер страницы меняется, но список продолжает показывать первую страницу.
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> </> );}Чем отличаются
staleTimeиgcTimeв TanStack Query и какие у них значения по умолчанию?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…Чем в TanStack Query v5 отличаются флаги
isPending,isFetchingиisLoadingи когда какой использовать?Когда TanStack Query автоматически перезапрашивает данные и как этим управлять?
Как в TanStack Query заставить один запрос дождаться другого (зависимые запросы)?
import { skipToken, useQuery } from '@tanstack/react-query'; function Projects({ email }: { email: string }) { const { data: user } = useQuery({ queryKey: ['user', email], queryFn: () => getUserByEmail(email),…Как изменять данные через
useMutationи как после этого держать закэшированные списки в актуальном состоянии?import { useMutation, useQueryClient } from '@tanstack/react-query'; function AddTodo() { const queryClient = useQueryClient(); const addTodo = useMutation({ mutationFn: (title: string) => api.createTodo(title),…Найдите баг: после добавления новой задачи список не показывает её, пока страницу не перезагрузят.
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 })} />;}Как сделать оптимистичное обновление в TanStack Query, включая откат при ошибке?
type Todo = { id: number; title: string; done: boolean }; function useToggleTodo() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (todo: Todo) => api.updateTodo({ ...todo, done: !todo.done }),…Как реализовать бесконечный скролл с TanStack Query?
import { useInfiniteQuery } from '@tanstack/react-query'; type Page = { items: Post[]; nextCursor: string | null }; function Feed() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({…Как обрабатывать ошибки при загрузке данных в React: повторные попытки, error boundaries и ошибки фонового обновления?
Как отменить запрос, который больше не нужен, например когда пользователь ушёл со страницы или ввёл новый поисковый запрос?
function SearchResults({ query }: { query: string }) { const [results, setResults] = useState<Hit[]>([]); useEffect(() => { const controller = new AbortController(); fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })…Как обновить истёкший access-токен, если несколько запросов одновременно получили 401?
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) => {…Как в React 19 работает Suspense для загрузки данных и как использовать его с 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({…Найдите баг: компонент навсегда застревает на fallback от Suspense, а во вкладке Network на каждый рендер появляется новый запрос.
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> );}Что такое водопад запросов (request waterfall) в React-приложении и как его избежать?
Как предзагрузить данные на сервере и гидратировать их в TanStack Query на клиенте (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() {…