REACT QUERY İLE SERVER STATE YÖNETMEK

Vimaj10 dk okuma
React mavi atom logosu cache kutusu sync ok ve loading success error query state chip'lerinin minimal kompozisyonu

React Query (TanStack Query); sunucudan gelen "server state"i cache, automatic refetch, stale-while-revalidate, optimistic update ve prefetching özellikleriyle yöneten kütüphanedir. Bu yazıda useQuery ve useMutation hook'larını, cache stratejisini ve modern patterns'i kod örnekleriyle öğreneceksiniz.

1. Server state nedir?

Frontend state iki kategoriye ayrılır:

  • UI state: Modal açık mı, dropdown seçimi, form input değeri. Lokal, kullanıcıya ait.
  • Server state: API'den gelen veri (ürünler, kullanıcı bilgisi, yorumlar). Uzak, paylaşılan, async.

useState ve useReducer UI state için ideal; ama server state için yeterli değil. Cache, sync, refetch, error retry gibi gereksinimler var; bu hook'ların temel davranışını ve sınırlarını netleştirmek için React resmi öğrenme kaynakları iyi bir başlangıç noktası. React Query bu boşluğu, hook'ların bıraktığı yerden devralarak doldurur.

2. Klasik yaklaşım vs React Query

Klasik (useEffect ile)

function ProductList() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch('/api/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>Yükleniyor...</p>;
  if (error) return <p>Hata: {error.message}</p>;
  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

React Query ile

import { useQuery } from '@tanstack/react-query';

function ProductList() {
  const { data: products, isLoading, error } = useQuery({
    queryKey: ['products'],
    queryFn: () => fetch('/api/products').then(r => r.json())
  });

  if (isLoading) return <p>Yükleniyor...</p>;
  if (error) return <p>Hata: {error.message}</p>;
  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

Daha kısa, daha temiz ve otomatik özellikler: cache, refetch on focus, retry on error, dedup edilmiş istekler.

3. Kurulum ve setup

npm install @tanstack/react-query
// main.tsx veya App.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

QueryClientProvider tüm uygulamayı sarar; React Query bunun altındaki tüm component'lerde kullanılabilir.

4. useQuery temelleri

const { data, isLoading, isError, error, refetch } = useQuery({
  queryKey: ['products', categoryId],
  queryFn: () => fetch(`/api/products?category=${categoryId}`).then(r => r.json()),
  staleTime: 5 * 60 * 1000, // 5 dakika boyunca taze
  enabled: !!categoryId // categoryId varsa çalış
});

Önemli parametreler

  • queryKey: Cache anahtarı; array olarak verilir, dependency'leri içerir
  • queryFn: Veri çeken fonksiyon; Promise döner
  • staleTime: Veri "taze" sayıldığı süre; bu süre içinde refetch yapılmaz
  • gcTime: Cache'te tutulma süresi (eski cacheTime); kullanılmayan veri sonra silinir
  • enabled: Query'nin çalışıp çalışmayacağı; conditional fetching

5. Cache stratejisi

React Query'nin en güçlü özelliği akıllı cache. Aynı queryKey ile çağrılan tüm component'ler aynı cache'i paylaşır.

// Component A
const { data } = useQuery({ queryKey: ['user', 123], queryFn: ... });

// Component B (farklı component, aynı queryKey)
const { data } = useQuery({ queryKey: ['user', 123], queryFn: ... });

// İkincisi network isteği yapmaz; cache'den okur

Stale-while-revalidate

Veri staleTime'ı geçtikten sonra: cache'den hemen göster + arka planda yeni veri çek + güncelle. Kullanıcı boş ekran görmez.

Automatic refetch triggers

  • Window focus: Kullanıcı tab'a geri döndüğünde
  • Reconnect: İnternet bağlantısı tekrar kurulduğunda
  • Mount: Component yeniden mount olduğunda (cache stale ise)
  • Interval: refetchInterval ile periyodik
useQuery({
  queryKey: ['data'],
  queryFn: fetchData,
  refetchOnWindowFocus: true, // varsayılan true
  refetchInterval: 30000, // her 30 saniye
  refetchOnMount: 'always' // her mount'ta
});
VS Code dark theme TSX editöründe useQuery hook'unun queryKey queryFn staleTime ve enabled parametreleriyle yapılandırıldığı kod bloğu syntax highlight ile

6. useMutation: veri değiştirme

POST, PUT, DELETE gibi side-effect'li işlemler için.

import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateProduct() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newProduct) => {
      return fetch('/api/products', {
        method: 'POST',
        body: JSON.stringify(newProduct)
      });
    },
    onSuccess: () => {
      // Cache'i invalidate et; products listesi yenilensin
      queryClient.invalidateQueries({ queryKey: ['products'] });
    }
  });

  return (
    <button
      onClick={() => mutation.mutate({ name: 'Yeni Ürün' })}
      disabled={mutation.isPending}
    >
      {mutation.isPending ? 'Ekleniyor...' : 'Ekle'}
    </button>
  );
}

7. Optimistic update

Kullanıcı butonna basınca anında UI'ı güncelle; API yanıtını bekleme. Hata olursa geri al.

const mutation = useMutation({
  mutationFn: updateTodo,

  // Mutation başlamadan önce UI'ı güncelle
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    const previousTodos = queryClient.getQueryData(['todos']);

    queryClient.setQueryData(['todos'], (old) =>
      old.map(todo => todo.id === newTodo.id ? newTodo : todo)
    );

    return { previousTodos };
  },

  // Hata olursa geri al
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previousTodos);
  },

  // Başarı veya hata sonrası refetch
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  }
});

Kullanıcı tıklama ile beyaz ekran görmez; anında değişikliği gözlemler. Modern web uygulamalarının "hızlı" hissi bu pattern'den gelir.

8. Infinite scroll / pagination

import { useInfiniteQuery } from '@tanstack/react-query';

function PostsList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: ({ pageParam = 0 }) =>
      fetch(`/api/posts?page=${pageParam}`).then(r => r.json()),
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    initialPageParam: 0
  });

  return (
    <div>
      {data?.pages.map((page) =>
        page.posts.map((post) => (
          <div key={post.id}>{post.title}</div>
        ))
      )}

      <button
        onClick={() => fetchNextPage()}
        disabled={!hasNextPage || isFetchingNextPage}
      >
        {isFetchingNextPage ? 'Yükleniyor...' : 'Daha fazla'}
      </button>
    </div>
  );
}

9. Prefetching

Kullanıcı bir link'e hover yapınca, henüz tıklamadan veriyi önceden çek. Tıklayınca anında render.

function ProductCard({ productId }) {
  const queryClient = useQueryClient();

  const prefetch = () => {
    queryClient.prefetchQuery({
      queryKey: ['product', productId],
      queryFn: () => fetchProduct(productId)
    });
  };

  return (
    <Link to={`/products/${productId}`} onMouseEnter={prefetch}>
      Ürün Detayı
    </Link>
  );
}

10. DevTools

React Query DevTools development sırasında cache durumunu görsel olarak gösterir.

import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

Hangi query'ler aktif, hangi durumda, ne kadar süre geri kalan; hepsi görünür. Debug için kritik.

11. Dikkat Edilecek Tuzaklar

  1. queryKey'i değişken olarak vermemek. Filter veya pagination değişiyorsa key'e eklemeli
  2. staleTime'ı varsayılan (0) bırakmak. Her mount refetch yapar; gereksiz istek
  3. Mutation sonrası invalidate unutmak. Eski veri ekrandakalır
  4. useQuery'i conditional koymak. enabled prop kullanılmalı, if koşulu değil
  5. Aynı queryFn'i birden fazla yerde tekrarlamak. Custom hook'a sarmalamak temiz

12. Custom hook pattern

useQuery'leri custom hook'a sarmalamak okunabilirlik ve tekrar kullanımı artırır:

// hooks/useProducts.ts
export function useProducts(categoryId?: string) {
  return useQuery({
    queryKey: ['products', categoryId],
    queryFn: () => fetchProducts(categoryId),
    enabled: !!categoryId,
    staleTime: 5 * 60 * 1000
  });
}

// Component
function ProductList({ categoryId }) {
  const { data, isLoading } = useProducts(categoryId);
  // ...
}
React Query DevTools panelinde aktif query listesi fresh stale fetching paused durum chip'leri ve seçili query'nin JSON cache içerik dökümü

13. Atılması Gereken Adım

React Query modern React ekosisteminin temel kütüphanelerinden. Bu alanda derinleşmek için kapsamlı React eğitimi programları state yönetimi ve veri akışı konularını birlikte aktarır.

14. Önemli Noktalar

React Query (TanStack Query); React'ta server state yönetiminin modern standardı. useState + useEffect boilerplate'ini kaldırır; cache, refetch, optimistic update, infinite scroll, prefetching özelliklerini hazır sunar. useQuery veri çeker; useMutation veri değiştirir. queryKey ile cache yönetimi, staleTime ile fresh-stale dengesi, invalidateQueries ile cache yenileme temel araçlar. DevTools, development sürecinin en çok iş gören yardımcısı. Modern React projelerinin yaklaşık yüzde 70'i kullanıyor.

Sıkça Sorulan Sorular

React Query mi Redux mu?

İki farklı şey. Redux client state (UI state, kullanıcı tercihleri) için; React Query <strong>server state</strong> (API'den gelen veri) için. Aslında karşılaştırma değil; aynı projede ikisi birden kullanılır. Modern projeler client state için Zustand veya Jotai (Redux'tan daha basit), server state için React Query tercih ediyor. Eski Redux + Redux Saga / Thunk yaklaşımı yerini bu ikiliye bıraktı.

staleTime ve gcTime arasındaki fark nedir?

staleTime veri 'taze' sayıldığı süre; bu süre içinde refetch yapılmaz, cache'den okunur. gcTime (eski cacheTime) ise cache'te tutulma süresi; veri kullanılmadığında sonra silinir. Tipik değerler: staleTime 1-5 dakika (verinin değişme sıklığına bağlı); gcTime 5-30 dakika. staleTime düşük + gcTime yüksek = sık refetch ama uzun cache.

Optimistic update gerçekten gerekli mi?

UX için çok değerli ama her yerde gerekli değil. Sık yapılan, hızlı tepki beklenen işlemler için ideal: like butonu, todo tamamlama, sepete ekleme. Ödeme, kayıt, kritik veri güncellemeleri için optimistic update gerekli değil; kullanıcı gerçek sunucu yanıtı bekler. Optimistic update geri alma (rollback) mekanizması karmaşıklık ekler; basit projelerde overkill olabilir.

queryKey nasıl tasarlanır?

Array olarak ve dependency'leri içermeli. Örnek: ['products', categoryId, page]. categoryId veya page değişirse otomatik yeni query oluşur. Tek string ([\'products\']) tüm filtreler için aynı cache; yanlış. İyi pattern: ['resource', { filters }, otherDeps]. queryKey hierarchical olabilir; invalidateQueries(['products']) tüm products query'lerini etkiler.

Infinite scroll pagination'dan ne kadar farklı?

Pagination klasik 'Sayfa 1, 2, 3' butonları; infinite scroll kullanıcı aşağı kaydırdıkça otomatik yeni veri yüklenir. useInfiniteQuery her ikisi için de kullanılır; fark UI'da. fetchNextPage manuel buton tıklamasıyla çağrılırsa pagination, IntersectionObserver ile otomatik çağrılırsa infinite scroll. Modern sosyal medya akışları infinite scroll standardını izler.

React Query Server-Side Rendering ile çalışır mı?

Evet, hatta Next.js ile entegrasyon için özel API'ları var. dehydrate ve hydrate ile sunucuda fetch edilen veri client'a aktarılır; kullanıcı sayfa açıldığında veri zaten cache'te. Next.js 13+ App Router ile <strong>React Query</strong> ek olarak kullanılabilir; built-in fetch caching ile birlikte. SSR + React Query SEO için önemli.

DevTools production'da kullanılır mı?

Hayır, sadece development için. ReactQueryDevtools component'i otomatik olarak NODE_ENV='production' ise devre dışı; ekstra ayar gerekmez. Development'ta cache durumunu görsel izlemenin en pratik yolu; query'lerin ne zaman tetiklendiği, cache nasıl güncellendiği, hata durumları hepsi görünür. Debug süresini ciddi şekilde azaltır.

 Vimaj