Доки по разработке
This project is maintained by teniryte
В TanStack Query есть два основных сценария:
useQueryuseInfiniteQueryconst projectsQuery = useQuery({
queryKey: ['projects', { page, pageSize, status }],
queryFn: () => fetchProjects({ page, pageSize, status }),
placeholderData: (previousData) => previousData,
})
Это современный v5-паттерн для плавного перехода между страницами. Исторический helper keepPreviousData больше не является центральной рекомендацией; основной способ сегодня это placeholderData: (prev) => prev.
placeholderDataisPlaceholderData помогает понять, что сейчас показаны временные данныеif (query.isPlaceholderData) {
// например, можно временно блокировать кнопку Next
}
queryKeyПравильно:
['projects', { page, pageSize, status }]
Неправильно:
page только в локальном stateИначе страницы будут смешиваться в кеше.
useEffect(() => {
if (!query.data?.hasMore) return
void queryClient.prefetchQuery({
queryKey: ['projects', { page: page + 1, pageSize, status }],
queryFn: () => fetchProjects({ page: page + 1, pageSize, status }),
staleTime: 30_000,
})
}, [page, pageSize, status, query.data?.hasMore, queryClient])
Это уменьшает perceived latency при клике “Next”.
import { useInfiniteQuery } from '@tanstack/react-query'
const messagesQuery = useInfiniteQuery({
queryKey: ['messages', roomId],
initialPageParam: null as string | null,
queryFn: ({ pageParam, signal }) =>
fetchMessages({ roomId, cursor: pageParam, signal }),
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
})
Ключевые поля:
data.pagesdata.pageParamsfetchNextPagehasNextPageisFetchingNextPagereturn (
<>
{messagesQuery.data.pages.flatMap((page) =>
page.items.map((message) => (
<MessageRow key={message.id} message={message} />
)),
)}
<button
onClick={() => messagesQuery.fetchNextPage()}
disabled={!messagesQuery.hasNextPage || messagesQuery.isFetchingNextPage}
>
{messagesQuery.isFetchingNextPage ? 'Loading...' : 'Load more'}
</button>
</>
)
maxPagesЕсли лента очень длинная, не держите весь стек страниц бесконечно:
useInfiniteQuery({
queryKey: ['feed'],
initialPageParam: null,
queryFn: ({ pageParam }) => fetchFeed(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor,
maxPages: 10,
})
Это полезно для чатов, activity feed и мобильных интерфейсов.
usePrefetchInfiniteQueryДля Suspense и предварительной загрузки во время render-подготовки:
usePrefetchInfiniteQuery({
queryKey: ['feed'],
initialPageParam: null,
queryFn: ({ pageParam }) => fetchFeed(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
Этот hook ничего не возвращает. Его задача только прогреть кеш до useSuspenseInfiniteQuery.
Правило:
queryKey, initialPageParam и логика getNextPageParamИначе гидратация будет непредсказуемой, а первая страница может запроситься повторно.
queryClient.setQueryData(['messages', roomId], (old: InfiniteData<MessagesPage>) =>
old
? {
...old,
pages: old.pages.map((page, index) =>
index === 0
? { ...page, items: [newMessage, ...page.items] }
: page,
),
}
: old,
)
Для insert/delete в середине списка часто проще и безопаснее инвалидировать, чем пытаться идеально патчить все страницы.
IntersectionObserverОбычный паттерн:
fetchNextPage()При больших списках почти всегда стоит комбинировать это с виртуализацией вроде @tanstack/react-virtual.
initialPageParammaxPages