ViraStack
  • Akademi
  • Hakkında
  • Destek
Genel BakışKaynaklarKatkı
ViraStack

Frontend ekosistemi: ücretsiz rehberler, proje şablonları, AI kuralları ve form bileşenleri.

Ürünler

  • ViraStack Start
  • ViraStack AI
  • ViraStack Mask
  • ViraStack Password
  • ViraStack Guide

Keşfet

  • Akademi
  • Hakkında
  • Marka
  • Destek

© 2026 ViraStack.

Açık kaynak · MIT

TanStack Query

09 / 12

Sayfalama ve sonsuz liste

Sayfa numarasını anahtara koymak, keepPreviousData, sonraki sayfayı önceden yüklemek ve useInfiniteQuery ile 'daha fazla yükle'.

Sayfa Sayfa Listelemek

Sayfalama ek bir API gerektirmez: sayfa numarasını query key'e koyarsınız.1

export const productQueries = {
  // ...
  page: (page: number) =>
    queryOptions({
      queryKey: ["products", "list", { page }],
      queryFn: () => fetchProductPage(page), // { items, hasMore }
    }),
};

Bu haliyle çalışır ama her sayfa yeni bir sorgudur. "Sonraki"ye bastığınızda liste kaybolur, yükleme ekranı gelir, sonra yeni sayfa görünür. Ekran her sayfada zıplar.1

keepPreviousData

placeholderData: keepPreviousData verirseniz yeni sayfa gelene kadar önceki sayfa ekranda kalır:1

import { keepPreviousData, useQuery } from "@tanstack/react-query";

function ProductTable() {
  const [page, setPage] = useState(1);
  const { data, isPending, isError, error, isFetching, isPlaceholderData } = useQuery({
    ...productQueries.page(page),
    placeholderData: keepPreviousData,
  });

  if (isPending) return <p>Yükleniyor...</p>;
  if (isError) return <p>Hata: {error.message}</p>;

  return (
    <>
      <ul style={{ opacity: isPlaceholderData ? 0.5 : 1 }}>
        {data.items.map((product) => <li key={product.id}>{product.name}</li>)}
      </ul>
      <button onClick={() => setPage((p) => p - 1)} disabled={page === 1}>
        Önceki
      </button>
      <span>Sayfa {page}</span>
      <button
        onClick={() => setPage((p) => p + 1)}
        disabled={isPlaceholderData || !data.hasMore}
      >
        Sonraki
      </button>
      {isFetching && <span> Güncelleniyor...</span>}
    </>
  );
}

isPlaceholderData, ekrandaki verinin önceki sayfadan kaldığını söyler. Yeni sayfa gelene kadar "Sonraki"yi kapatmak ve listeyi soluklaştırmak için kullanın.1 keepPreviousData aslında (previousData) => previousData fonksiyonudur; placeholderData kendisi cache'e yazılmaz (bkz. Cache ve stale time).

Kullanıcı büyük ihtimalle sonraki sayfaya geçecek. Bir sonraki sayfayı arka planda yükleyin ki tıkladığında hazır olsun:2

const queryClient = useQueryClient();

useEffect(() => {
  if (!isPlaceholderData && data?.hasMore) {
    void queryClient.query(productQueries.page(page + 1)).catch(noop);
  }
}, [data, isPlaceholderData, page, queryClient]);

Sayfa numarasını useState yerine URL'de (?page=2) tutarsanız sayfa paylaşılabilir olur ve tarayıcının geri butonu çalışır. Anahtar aynı kalır; değişen yalnızca değerin nereden okunduğudur.3

Sonsuz Liste

"Daha fazla yükle" butonu ya da kaydırınca yüklenen listeler için useInfiniteQuery var. Tek farkı, verinin sayfalardan oluşan bir yapı olmasıdır:4

import { infiniteQueryOptions } from "@tanstack/react-query";

export const reviewQueries = {
  infinite: (productId: number) =>
    infiniteQueryOptions({
      queryKey: ["reviews", productId, "infinite"],
      queryFn: ({ pageParam }) => fetchReviews(productId, pageParam), // { items, nextCursor }
      initialPageParam: 0,
      getNextPageParam: (lastPage) => lastPage.nextCursor,
    }),
};
AlanNe işe yarar
initialPageParamİlk sayfanın parametresi; v5'te zorunlu
getNextPageParamSon sayfadan bir sonrakinin parametresini çıkarır; undefined ya da null dönerse liste biter
data.pagesGelen sayfaların dizisi
fetchNextPageSonraki sayfayı getirir
hasNextPagegetNextPageParam bir değer döndürüyorsa true
isFetchingNextPageYalnızca sonraki sayfa yüklenirken true; arka plan yenilemesinden ayırmak için
function Reviews({ productId }: { productId: number }) {
  const { data, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage, status } =
    useInfiniteQuery(reviewQueries.infinite(productId));

  if (status === "pending") return <p>Yükleniyor...</p>;
  if (status === "error") return <p>Yorumlar yüklenemedi.</p>;

  return (
    <>
      {data.pages.flatMap((page) => page.items).map((review) => (
        <p key={review.id}>{review.text}</p>
      ))}
      <button
        onClick={() => fetchNextPage()}
        disabled={!hasNextPage || isFetching}
      >
        {isFetchingNextPage ? "Yükleniyor..." : hasNextPage ? "Daha fazla" : "Hepsi bu kadar"}
      </button>
    </>
  );
}

Butonu isFetching sürerken kapatmak önemlidir. Bir sonsuz listede aynı anda tek bir istek olabilir; tüm sayfalar tek cache girdisini paylaşır. Arka plan yenilemesi sürerken fetchNextPage çağırmak veriyi ezebilir.4 Kaydırınca yüklemek için butonu bir IntersectionObserver ile tetiklerken de aynı kontrolü yapın: hasNextPage && !isFetching && fetchNextPage().4

Sonsuz Liste Nasıl Yenilenir

Sonsuz liste cache'te tek bir sorgudur; sayfalar birbirine bağlı bir liste gibi durur. Her sayfa bir öncekinden aldığı parametreyle getirilir.5 Bu yüzden liste eskiyip yenilendiğinde sayfalar ilk sayfadan başlayarak sırayla yeniden çekilir. Böylece arada eklenen ya da silinen kayıtlar yüzünden eski cursor'larla tekrar eden ya da atlanan kayıt oluşmaz.45

Bunun bedeli var: kullanıcı yirmi sayfa yüklediyse yenileme yirmi istek demektir. maxPages cache'te tutulan sayfa sayısını sınırlar. Bunu kullanırsanız iki yönde de gezinebilmek için getPreviousPageParam da vermeniz gerekir:4

infiniteQueryOptions({
  // ...
  getPreviousPageParam: (firstPage) => firstPage.prevCursor,
  maxPages: 3,
});

API cursor döndürmüyorsa getNextPageParam üçüncü argüman olarak son sayfanın parametresini alır; lastPageParam + 1 ile sayfa numarasını kendiniz hesaplarsınız.4

Hangisi

DurumÖneri
Tablo, admin paneli, kullanıcı belirli bir sayfaya gidecekSayfalı sorgu + keepPreviousData
Akış, yorumlar, arama sonuçlarıuseInfiniteQuery
Çok uzun liste, binlerce satıruseInfiniteQuery + maxPages + sanallaştırma

Binlerce satırı DOM'da tutmak sorgudan bağımsız bir sorundur; sanallaştırma için Render performansı dersine bakın.

Kendin Dene

  • Ürün tablosunu keepPreviousData olmadan yazın, sayfalar arasında geçin. Sonra ekleyin; farkı not edin.

  • Sonraki sayfayı önceden yükleyin ve Network sekmesinde ne zaman istek gittiğini izleyin.

  • Yorumlara sonsuz liste ekleyin. Üç sayfa yükleyin, sekmeden çıkıp dönün; kaç istek gidiyor ve hangi sırayla?

  • maxPages: 2 verip tekrar deneyin.

Detaylı Öğrenmek İçin

  • Resmi doküman: "Infinite Queries"; iki yönlü liste, sayfaları elle güncelleme ve maxPages.4

  • Yazı: TkDodo, "How Infinite Queries work"; sonsuz sorgunun cache'teki yapısı ve yenileme davranışı.5

  • Resmi doküman: "Paginated / Lagged Queries"; placeholderData ve isPlaceholderData.1

Kaynaklar

  • 1DokümanTanStack Query: Paginated / Lagged Queries

    Sayfayı anahtara koymak, `keepPreviousData` ve `isPlaceholderData`.

  • 2DokümanTanStack Query: Prefetching & Router Integration

    `queryClient.query` ile önceden yükleme.

  • 3VideoTkDodo

    : — React Day Berlin, Aralık 2024. Filtreyi URL'de tutup anahtara bağlamak.

  • 4DokümanTanStack Query: Infinite Queries

    `useInfiniteQuery`, sıralı yenileme, eşzamanlı istek uyarısı ve `maxPages`.

  • 5MakaleTkDodo

    : — 14 Eylül 2024. Sonsuz sorgunun tek cache girdisi olarak yapısı ve `QueryBehavior`.

Bu sayfayı düzenle

Son güncelleme tarihi: 21 Ekim 2026

Önceki adım08. Bağımlı ve paralel sorgularSonraki adım10. Suspense ve hata yönetimi