REACT QUERY İLE SERVER STATE YÖNETMEK

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 okurStale-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
});
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
- queryKey'i değişken olarak vermemek. Filter veya pagination değişiyorsa key'e eklemeli
- staleTime'ı varsayılan (0) bırakmak. Her mount refetch yapar; gereksiz istek
- Mutation sonrası invalidate unutmak. Eski veri ekrandakalır
- useQuery'i conditional koymak. enabled prop kullanılmalı, if koşulu değil
- 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);
// ...
}
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.


