React Query Temel Kavramlar ve Mimari

React Query, React uygulamalarında sunucu durumunu yönetmek için tasarlanmış bir kütüphanedir. Veri çekme (fetching), önbelleğe alma (caching), senkronizasyon ve sunucu durumu güncellemelerini yönetme süreçlerini basitleştirir. Geleneksel olarak bu işlemler, karmaşık `useEffect` hook'ları ve global state yönetimi araçlarıyla yönetilirdi. React Query, bu yaklaşımı değiştirerek sunucudan gelen verilerin birinci sınıf vatandaş olmasını sağlar.

Kütüphanenin en önemli iki temel kavramı Sorgular (Queries) ve Mutasyonlar (Mutations)'dır. Sorgular, genellikle GET istekleri aracılığıyla veri almak için kullanılırken, mutasyonlar POST, PUT, PATCH, DELETE gibi sunucu durumunu değiştiren işlemleri kapsar. Bu iki soyutlama, veri akışını net bir şekilde ayırır ve uygulama mantığını anlaşılır kılar.

React Query'nin mimarisi, merkezi bir "QueryClient" etrafında döner. Bu istemci, uygulamanın kök seviyesinde sağlanır (Provider aracılığıyla) ve tüm sorguların ve mutasyonların durumunu, önbelleğini ve konfigürasyonunu yönetir. QueryClient, aynı zamanda önbelleği manuel olarak manipüle etmek, sorguları geçersiz kılmak (invalidate) veya önceden getirmek (prefetch) için güçlü bir API sunar.

Önbellek mekanizması, kütüphanenin kalbinde yer alır. Her sorgu, benzersiz bir anahtar (query key) ile tanımlanır ve bu anahtarla ilişkilendirilmiş bir önbellek girişi oluşturur. Bu önbellek, aynı anahtara sahip yeni bileşenlerin aynı veriye hızlıca erişmesini ve gereksiz ağ isteklerinin önlenmesini sağlar. Önbellek stratejisi, varsayılan davranışları ve konfigürasyon seçenekleri ile oldukça esnektir.

Kütüphane, tüm bu işlemleri arka planda otomatik olarak yönetirken, bileşenlere kapsamlı bir meta durum (metadata) sunar. Bu durumlar, bir sorgunun yükleniyor olup olmadığını, başarılı veya başarısız olduğunu, ne zaman yeniden denenebileceğini ve verilerin ne kadar süredir önbellekte kaldığını anlamak için kritiktir.

Kavram Tanım Ana API
Sorgu (Query) Sunucudan asenkron veri getirme işlemi. useQuery
Mutasyon (Mutation) Sunucu durumunu değiştiren işlem. useMutation
Sorgu Anahtarı (Query Key) Sorguyu benzersiz tanımlayan dizi (array). ['posts', id]
Sorgu İstemcisi (QueryClient) Sorguları ve önbelleği yöneten merkezi nesne. QueryClient
Önbellek (Cache) Daha önce getirilmiş sorgu sonuçlarının saklandığı yer. QueryClient içinde yönetilir.

Bu temel mimari, geliştiricilere karmaşık durum yönetimi mantığı yazmaktan ziyade, kullanıcı arayüzünün durumunu ve deneyimini iyileştirmeye odaklanma özgürlüğü tanır. React Query, performans ve kullanıcı deneyimini artırmak için gereken altyapıyı görünmez bir şekilde sağlar.

Temel Veri Çekme (Fetching) İşlemleri

React Query'de veri çekme işlemi, `useQuery` hook'u kullanılarak gerçekleştirilir. Bu hook, bir sorgu anahtarı (query key) ve bir sorgu fonksiyonu (query function) alır. Sorgu fonksiyonu, veriyi getiren ve bir Promise döndüren fonksiyondur (genellikle `fetch` veya `axios` çağrısı). Hook, bileşene sorgunun durumu (`isLoading`, `error`) ve sonuç verisini (`data`) sağlar.

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

function fetchUserData(userId) {
  return fetch(`/api/users/${userId}`).then(res => res.json());
}

function UserProfile({ userId }) {
  const { data, isLoading, error, isError } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUserData(userId),
  });

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

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
}

Sorgu anahtarı, yalnızca bir string değil, genellikle bir dizidir. Bu dizi, sorguyu benzersiz bir şekilde tanımlamak ve önbellekteki konumunu belirlemek için kullanılır. Bağımlı değişkenler (userId gibi) anahtarın bir parçası yapılarak, her bir değişim için ayrı bir önbellek girişi oluşturulur. Bu, önbellek yönetiminin temel taşıdır.

`useQuery` hook'u, arka planda otomatik olarak güçlü yeniden deneme (retry) ve yeniden getirme (refetch) stratejileri uygular. Varsayılan olarak, bir pencere yeniden odaklandığında veya ağ yeniden bağlandığında arka planda verileri yeniler. Bu davranışlar, `refetchOnWindowFocus`, `refetchOnReconnect` ve `retry` gibi seçeneklerle özelleştirilebilir. Bu sayede, kullanıcıya her zaman güncel veri gösterilmesi büyük ölçüde otomatikleşir.

Sorgu durumlarını yönetmek için kullanılan temel özellikler şunlardır: `isLoading` (ilk veri yüklenirken true), `isFetching` (arka plan yenilemeleri dahil herhangi bir getirme işleminde true), `isError`, `error`, `isSuccess` ve `data`. Bu özellikler, kullanıcı arayüzünde yükleniyor göstergeleri, hata mesajları ve başarılı içerik render'ı için kullanılır.

Durum Özelliği Kullanım Amacı Not
isLoading İlk veri yüklenirken gösterge göstermek. Önbellekte veri yoksa true.
isFetching Arka plan yenilemeleri sırasında ince bir gösterge. Yeniden getirme sırasında true olur.
isError Bir hata oluştuğunda kullanıcıyı bilgilendirmek. -
data Başarılı sorgu sonucunu render etmek. Tanımsız (undefined) olabilir.
error Hata detayına erişmek ve loglamak. -
isSuccess Veri başarıyla geldikten sonra ek işlem yapmak. Veri mevcutsa true.

Temel veri çekme işlemlerinde bir diğer önemli konu, verilerin önceden getirilmesi (prefetching) ve varsayılan sorgular (default queries) belirleme yeteneğidir. QueryClient'ın `prefetchQuery` methodu, kullanıcı bir sayfaya tıklamadan önce verilerin önbelleğe yüklenmesini sağlayarak, gezinme deneyimini önemli ölçüde hızlandırır.

Mutasyonlar ile Veri Değiştirme

React Query'de sunucu durumunu değiştiren işlemler (create, update, delete) `useMutation` hook'u ile yönetilir. `useQuery`'den farklı olarak, mutasyonlar otomatik olarak tetiklenmez; genellikle bir kullanıcı etkileşimi (buton tıklaması gibi) sonrasında `mutate` fonksiyonunun çağrılmasıyla başlatılır. Bu hook, mutasyonun durumunu (`isLoading`, `isSuccess`, `isError`) ve sonucunu takip etmek için gerekli araçları sağlar.

Mutasyonların en güçlü yanı, başarılı olduklarında ilgili sorguları "geçersiz kılma" (invalidate) ve böylece güncel verilerin otomatik olarak yeniden getirilmesini tetikleme yeteneğidir. Bu, `onSuccess` callback'i içinde `queryClient.invalidateQueries({ queryKey: ['posts'] })` şeklinde yapılır. Bu yaklaşım, sunucu durumu değiştikten sonra kullanıcı arayüzünün manuel güncellenme zorunluluğunu ortadan kaldırır ve veri tutarlılığını garantiler.

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

function addNewPost(postData) {
  return fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify(postData),
  }).then(res => res.json());
}

function NewPostForm() {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: addNewPost,
    onSuccess: () => {
      // 'posts' anahtarına sahip tüm sorguları geçersiz kıl
      queryClient.invalidateQueries({ queryKey: ['posts'] });
      // Kullanıcıya bildirim göster
      alert('Gönderi başarıyla eklendi!');
    },
    onError: (error) => {
      alert(`Bir hata oluştu: ${error.message}`);
    }
  });

  const handleSubmit = (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);
    const postData = Object.fromEntries(formData);
    mutation.mutate(postData); // Mutasyonu tetikle
  };

  return (
    
{/* Form alanları */}
); }

Mutasyon sonuçlarını iyimser güncelleme (optimistic updates) ile işlemek, kullanıcı deneyimini önemli ölçüde artırır. Bu teknik, sunucudan yanıt gelmeden önce, başarılı olacağı varsayılarak kullanıcı arayüzünü günceller. Eğer mutasyon başarısız olursa, önceki duruma otomatik olarak geri alınır (rollback). Bu, `onMutate`, `onError` ve `onSettled` callback'leri kullanılarak QueryClient'ın `setQueryData` ve `cancelQueries` methodları ile uygulanır.

`useMutation` hook'u, aynı zamanda değişkenleri sırayla veya paralel göndermek için `mutateAsync` fonksiyonunu da sunar. Bu, mutasyon sonucuna göre daha karmaşık akışları yönetmeyi (örneğin, bir sonraki adıma geçmeden önce mutasyonun bitmesini beklemek) kolaylaştırır. Ancak, durum yönetimi için `mutateAsync` yerine `mutate` kullanmak ve side effect'leri callback'lerde yönetmek genellikle daha temiz bir yaklaşımdır.

Mutasyon durumları, form gönderim butonlarını devre dışı bırakmak veya yükleniyor göstergeleri göstermek için kullanışlıdır. `isIdle`, `isLoading`, `isSuccess`, `isError` ve `isPaused` gibi özellikler, kullanıcıya geri bildirim sağlamak için doğrudan kullanılabilir. Bu durum yönetimi, geliştiricinin işini büyük ölçüde kolaylaştırır.

Mutasyonlar, yerel önbelleği doğrudan güncelleyerek ağ isteği gecikmesini maskeleyebilir. Örneğin, yeni bir gönderi eklerken, `onMutate` callback'inde `setQueryData` ile önbelleğe eklenebilir. Bu, kullanıcının eyleminin anında etkisini görmesini sağlar ve uygulamayı daha hızlı ve duyarlı hissettirir. Başarısız olursa, otomatik geri alınır.

Sorgu Durumu ve Performans Optimizasyonları

React Query, varsayılan olarak bir dizi akıllı performans optimizasyonu getirir. En önemlilerinden biri, arka plan yeniden getirme (background refetch) davranışıdır. Bir bileşen yeni bağlandığında veya pencere yeniden odaklandığında, React Query önbellekteki veriyi hemen gösterir ve aynı zamanda arka planda sunucudan güncel veriyi getirir. Bu, kullanıcıya eski veriyi anında sunarken uygulamanın güncelliğini de korur.

Sorguların ne sıklıkla yenileneceğini kontrol etmek için `staleTime` ve `cacheTime` ayarları kritiktir. `staleTime`, getirilen bir verinin ne kadar süre "taze" (stale) olarak kabul edilmeyeceğini, yani arka plan yenilemesi yapılmadan doğrudan önbellekten kullanılabileceğini belirler. Varsayılan değer 0'dır, bu da verinin anında "bayat" kabul edilip yeni bir render'da arka planda yenileneceği anlamına gelir. Bunu artırmak gereksiz istekleri azaltır.

`cacheTime` ise, aktif bir bileşen tarafından kullanılmayan ("inaktif") bir sorgunun sonucunun önbellekte ne kadar süre tutulacağını belirler. Varsayılan 5 dakikadır. Bu süre dolduğunda, veri çöp toplama (garbage collection) ile bellekten temizlenir. Bu iki sürenin doğru ayarlanması, bellek kullanımı ve veri tazeliği arasında mükemmel bir denge kurar.

Optimizasyon API/Seçenek Açıklama ve Performans Etkisi
Arka Plan Yenileme refetchOnWindowFocus, refetchOnReconnect Kullanıcı deneyimini kesintisiz tutarken veriyi günceller. Ağ kullanımını artırabilir.
Veri Tazeliği staleTime (örn: 30_000 ms) Belirtilen süre boyunca gereksiz arka plan isteklerini engeller, performansı artırır.
Önbellek Ömrü cacheTime (örn: 10 * 60 * 1000 ms) Bellek kullanımını optimize eder, hızlı geri dönüşler sağlar.
Yeniden Deneme retry, retryDelay Geçici ağ hatalarında kullanıcı deneyimini iyileştirir, istek sayısını artırabilir.
Bölümleme keepPreviousData Sayfalar arasında gezinirken arayüzün sabit kalmasını sağlar, kullanıcı deneyimini artırır.

`keepPreviousData` seçeneği, sayfalama (pagination) veya sonsuz kaydırma (infinite scroll) gibi senaryolarda kullanıcı deneyimini muazzam iyileştirir. Yeni bir sayfa verisi getirilirken, önceki sayfanın verisi ekranda kalmaya devam eder. Böylece, kullanıcı arayüzü boş bir durum (loading state) göstermez ve daha akıcı hissedilir. Bu, performanstan ziyade algılanan performans üzerinde büyük bir etki yaratır.

Bir diğer güçlü optimizasyon, sorguların manuel olarak önceden getirilmesidir (prefetching). Örneğin, kullanıcı bir linkin üzerine geldiğinde (`onMouseEnter`), ilgili sayfanın verileri `queryClient.prefetchQuery` ile önbelleğe yüklenebilir. Kullanıcı linke tıkladığında, veri anında hazır olacağından yükleniyor göstergesi neredeyse görünmez hale gelir. Bu teknik, özellikle öngörülebilir kullanıcı akışlarında çok etkilidir.

Son olarak, React Query Devtools, tüm bu durumları ve önbellek içeriğini görselleştirerek optimizasyon kararlarınızı destekler. Hangi sorguların aktif, taze veya bayat olduğunu, ne zaman yenilendiklerini görmek, performans darboğazlarını ve gereksiz yeniden getirmeleri tespit etmek için paha biçilmezdir. Bu araç, karmaşık uygulamalarda bile durum yönetimini şeffaf hale getirir.

Sorgu Anahtarları ve Bağımlı Sorgular

Sorgu anahtarları (query keys), React Query'nin önbelleği yönetmek, verileri doğru yerden almak ve otomatik yeniden getirme işlemlerini tetiklemek için kullandığı benzersiz tanımlayıcılardır. Bir sorgu anahtarı, basit bir string olabilse de, genellikle bir dizi (array) şeklinde tanımlanır. Bu dizi, sorgunun bağımlılıklarını (dependency) ve kapsamını (scope) hiyerarşik olarak kodlar.

Örneğin, `['posts']` anahtarı tüm gönderileri, `['posts', 'popular']` popüler gönderileri, `['posts', 12]` ise ID'si 12 olan tek bir gönderiyi temsil edebilir. Bu yapı, QueryClient'ın `invalidateQueries({ queryKey: ['posts'] })` gibi bir çağrı yapıldığında, 'posts' ile başlayan tüm sorguları (alt anahtarları da içerecek şekilde) hedeflemesine olanak tanır. Bu, önbellek yönetiminde büyük bir esneklik ve güç sağlar.

// Farklı sorgu anahtarı yapıları
const allPostsKey = ['posts'];
const filteredPostsKey = ['posts', { filter: 'popular' }];
const singlePostKey = ['posts', postId];
const userPostsKey = ['users', userId, 'posts'];

// Kullanım örnekleri
useQuery({ queryKey: allPostsKey, queryFn: fetchPosts });
useQuery({ queryKey: filteredPostsKey, queryFn: () => fetchPosts('popular') });
useQuery({ queryKey: singlePostKey, queryFn: () => fetchPost(postId) });

Bağımlı sorgular (dependent queries), bir sorgunun yürütülmesinin başka bir sorgunun başarıyla tamamlanmasına veya belirli bir veri parçasının mevcudiyetine bağlı olması durumudur. Bu, `useQuery` hook'unun `enabled` seçeneği ile kolayca yönetilir. `enabled` false olarak ayarlandığında, sorgu otomatik olarak çalıştırılmaz; ancak bağımlı olduğu veri hazır olduğunda true yapılarak tetiklenir.

Bu özellik, klasik "waterfall" veri getirme sorununu React Query ile çözmenin temel yoludur. Örneğin, bir kullanıcının profilini getirdikten sonra, o kullanıcının gönderilerini getirmek istediğinizde, ikinci sorgunun `enabled` değeri ilk sorgudan gelen `userId`'nin varlığına bağlanır. Bu, veri bağımlılıklarını açık ve okunabilir bir şekilde ifade eder.

Sorgu anahtarlarının doğru yapılandırılması, önbellek verimliliğinin anahtarıdır. Çok geniş anahtarlar gereksiz yeniden getirmelere, çok spesifik anahtarlar ise önbellek israfına neden olabilir. Anahtar yapısı, uygulamanın veri erişim modelini yansıtmalıdır. Ayrıca, nesneler anahtarın bir parçası olduğunda, React Query'nin bunları sıralı olarak karşılaştırdığını (order-sensitive) unutmamak gerekir; `{a: 1, b: 2}` ile `{b: 2, a: 1}` farklı anahtarlar olarak değerlendirilir.

Gelişmiş Konfigürasyon ve Ölçeklendirme

Büyük ölçekli uygulamalarda React Query'yi etkin bir şekilde kullanmak, global konfigürasyon ve iyi tanımlanmış kalıplar gerektirir. QueryClient'ın merkezi yapılandırması, uygulamanın kökünde `defaultOptions` belirleyerek tüm sorgu ve mutasyonlar için tutarlı davranışlar sağlamanın en iyi yoludur. Bu, tekrarlayan kodları ortadan kaldırır ve uygulama çapında standartları korur.


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

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 dakika
      cacheTime: 10 * 60 * 1000, // 10 dakika
      retry: 2,
      refetchOnWindowFocus: false,
    },
    mutations: {
      retry: 1,
    },
  },
});

// Uygulama kökünde sağlama
<QueryClientProvider client={queryClient}>
  <App />
</QueryClientProvider>

Özel hook'lar (custom hooks) oluşturmak, veri getirme mantığını bileşenlerden soyutlamanın ve tekrar kullanmanın en temiz yöntemidir. Her bir veri kaynağı için (örn: `usePosts`, `useUser`) özel bir hook yazmak, sorgu anahtarlarını ve fonksiyonlarını merkezileştirir. Bu, bakımı kolaylaştırır ve bileşenlerin yalnızca ihtiyaç duydukları veri ve durumla ilgilenmesine izin verir.

Harici durum değişiklikleriyle entegrasyon için QueryClient'ın `setQueryData` ve `getQueryData` methodları doğrudan önbelleği okumak ve yazmak için kullanılabilir. Bu, bir WebSocket mesajı veya bir harici olay dinleyicisi gelen güncel veriyi doğrudan ilgili sorgunun önbellek girişine yazmak için kullanışlıdır. Bu, sunucudan tam bir yeniden getirme yapmadan kullanıcı arayüzünü anında günceller.

Sunucudan gelen yanıt şemalarını TypeScript ile tanımlamak, geliştirme sırasında tip güvenliği sağlar. `useQuery` ve `useMutation` hook'larına generics uygulanarak `data`, `error` ve `variables` tipleri net bir şekilde tanımlanabilir. Bu, veri yapısı değiştiğinde derleme zamanı hataları almanızı ve IDE otomatik tamamlamasından en iyi şekilde yararlanmanızı sağlar.

Son olarak, karmaşık durum senaryoları için React Query, diğer state yönetim araçlarıyla (Zustand, Redux) veya sunucu durumu olmayan yerel istemci durumuyla mükemmel bir şekilde bir arada çalışır. React Query'nin odak noktası sunucu durumu ve önbellektir; kullanıcı tercihleri veya form durumu gibi istemciye özgü durumlar için diğer çözümler kullanılmalıdır. Bu ayrımı anlamak, temiz ve ölçeklenebilir bir mimari kurmanın temelidir. React Query, büyük uygulamalarda veri katmanını yönetmek için sağlam, esnek ve test edilebilir bir temel sunar.