Списки даних — це хліб і масло веб-розробки. Але завантажувати 10,000 записів одразу — погана ідея. Нам потрібна пагінація або "нескінченна прокрутка".
TanStack Query перетворює це складне завдання на задоволення.
Припустимо, ми маємо звичайний запит:
const { data, isPending } = useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
});
Проблема: Коли користувач натискає "Next Page", page змінюється з 1 на 2.
['projects', 1] -> ['projects', 2].isPending: true.Рішення: Ми хочемо залишити дані сторінки 1 на екрані, поки завантажується сторінка 2.
У v5 для цього використовується placeholderData.
import { useQuery, keepPreviousData } from '@tanstack/react-query';
function ProjectsList() {
const [page, setPage] = useState(1);
const { data, isPending, isPlaceholderData } = useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
// ✨ МАГІЯ ТУТ
placeholderData: keepPreviousData,
});
return (
<div>
{isPending ? (
<div>Loading...</div>
) : (
<div style={{ opacity: isPlaceholderData ? 0.5 : 1 }}>
{data.items.map(project => (
<div key={project.id}>{project.name}</div>
))}
</div>
)}
<button onClick={() => setPage(old => old - 1)} disabled={page === 1}>
Previous
</button>
<button onClick={() => setPage(old => old + 1)}>
Next
</button>
</div>
);
}
isPending.isPlaceholderData: true.Щоб зробити UX ще кращим, ми можемо завантажити сторінку 2 заздалегідь, поки користувач ще дивиться на сторінку 1.
// В useEffect, коли змінюється сторінка
useEffect(() => {
if (!isPlaceholderData && data?.hasMore) {
queryClient.prefetchQuery({
queryKey: ['projects', page + 1],
queryFn: () => fetchProjects(page + 1),
});
}
}, [data, isPlaceholderData, page, queryClient]);
Тепер при натисканні "Next", дані з'являться миттєво, бо вони вже в кеші.
useInfiniteQuery)Пагінація "Завантажити ще" (як у Twitter/Instagram) вимагає іншого підходу. Нам потрібно не замінювати сторінку 1 на 2, а додавати сторінку 2 до сторінки 1.
Для цього є спеціальний хук useInfiniteQuery.
Ваш бекенд повинен повертати щось на зразок курсора для наступної сторінки:
{
"items": [...],
"nextCursor": 20
}
import { useInfiniteQuery } from '@tanstack/react-query';
function InfiniteFeed() {
const {
data, // Структура змінилась!
error,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
status,
} = useInfiniteQuery({
queryKey: ['feed'],
queryFn: ({ pageParam }) => fetchFeed(pageParam),
// Початковий курсор
initialPageParam: 0,
// Логіка отримання наступного курсора з відповіді бекенда
getNextPageParam: (lastPage, allPages) => {
// lastPage — це те, що повернув останній запит
return lastPage.nextCursor ?? undefined;
// Якщо повертаємо undefined, hasNextPage стане false
},
});
if (status === 'pending') return <div>Loading...</div>;
if (status === 'error') return <div>Error: {error.message}</div>;
return (
<div>
{/* data.pages — це масив масивів (масив сторінок) */}
{data.pages.map((group, i) => (
<React.Fragment key={i}>
{group.items.map((project) => (
<div key={project.id} className="card">
{project.name}
</div>
))}
</React.Fragment>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
<div>{isFetching && !isFetchingNextPage ? 'Background Updating...' : null}</div>
</div>
);
}
dataУ useQuery data — це просто відповідь.
У useInfiniteQuery data — це об'єкт:
{
pages: [responsePage1, responsePage2, ...],
pageParams: [param1, param2, ...]
}
Тому нам завжди треба робити подвійний map (спочатку по сторінках, потім по елементах сторінки).
v5 також підтримує двонаправлений скрол (вгору і вниз, як у месенджерах), використовуючи getPreviousPageParam.
Оптимістичні Оновлення: Швидше за Світло
Ви коли-небудь помічали, як працює лайк в Instagram або повідомлення в Telegram? Ви натискаєте кнопку, і серце стає червоним миттєво. Додаток не чекає, поки сервер скаже "ОК". Він оптимістично припускає, що все буде добре.
Просунуті Патерни та Оптимізація
Ви вже вмієте робити базові речі. Тепер перейдемо до технік "чорного поясу". Ці патерни допоможуть вирішити складні архітектурні завдання та оптимізувати продуктивність.