REACT QUERY İLE SERVER STATE YÖNETMEK
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 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
});
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
- 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
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);
// ...
}
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.



