Yazılarımız

OfisData

REACT QUERY İLE SERVER STATE YÖNETMEK

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

React'ta yıllarca veri yönetimi useState + useEffect + fetch ile yapıldı. Bu yaklaşım her component'te tekrar tekrar yazıldı: loading state, error state, refetch logic, cache invalidation. Tek bir API endpoint'i için 50 satır boilerplate kod. React Query (yeni adı TanStack Query) bu sorunu kökten çözdü.

React Query "server state"i (sunucudan gelen veri) tüm karmaşıklığıyla yöneten kütüphane. Cache, automatic refetch, stale-while-revalidate, optimistic update, infinite scroll, prefetching - hepsi tek bir hook ile. Modern React projelerinin yaklaşık yüzde 70'i kullanıyor.

Aşağıda React Query'nin nasıl çalıştığını, useQuery ve useMutation hook'larını, cache stratejisini ve modern patterns'i anlatıyoruz.

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.

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.

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.

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

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

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>
  );
}

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.

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>
  );
}

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>
  );
}

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.

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

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ü

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.

Ö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.

 Vimaj