Redux · Redux Toolkit · React Query
Üçü de "state yönetimi" kelimesinin altında toplanır ama iki farklı problemi çözerler: Redux/RTK client state için (UI'da tutulan, sunucudan gelmeyen veri — sepet, form, açık/kapalı paneller), React Query ise server state için (API'den gelen, cache'lenmesi ve senkronize kalması gereken veri).
Canlı demo — gerçek Redux Toolkit store'u
Bu, sahte bir animasyon değil — sayfanın kendisi @reduxjs/toolkit ve react-redux paketleriyle kurulu gerçek bir store çalıştırıyor. Butonlara bas, sağdaki panelde her tıklamanın hangi action'ı dispatch ettiğini canlı gör.
🛒 Mini sepet (gerçek @reduxjs/toolkit store)
Bu demonun kaynak kodu — 3 dosya
Redux Toolkit'in klasik üçlüsü: slice (state + reducer + action'lar bir arada), store (slice'ları birleştirir), component (hook'larla bağlanır).
// cartSlice.ts — bu sayfadaki canlı demoyu çalıştıran gerçek kod
import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
interface CartItem { id: string; name: string; price: number; qty: number }
interface CartState { items: CartItem[]; log: string[] }
const initialState: CartState = { items: [], log: [] };
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
// Redux Toolkit altında Immer var: state'i "mutate" ediyormuş gibi
// yazarsın ama aslında değişmez (immutable) bir kopya üretilir.
addItem(state, action: PayloadAction<{id:string; name:string; price:number}>) {
const existing = state.items.find(i => i.id === action.payload.id);
if (existing) existing.qty += 1;
else state.items.push({ ...action.payload, qty: 1 });
},
},
});
export const { addItem } = cartSlice.actions;
export default cartSlice.reducer;// store.ts
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./cartSlice";
export const store = configureStore({
reducer: { cart: cartReducer },
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;// bir component içinde kullanım
import { useDispatch, useSelector } from "react-redux";
import { addItem } from "./cartSlice";
import type { RootState, AppDispatch } from "./store";
function ProductCard({ product }) {
const dispatch = useDispatch<AppDispatch>();
const qty = useSelector((s: RootState) =>
s.cart.items.find(i => i.id === product.id)?.qty ?? 0
);
return <button onClick={() => dispatch(addItem(product))}>Sepete ekle ({qty})</button>;
}switch/case reducer'ları elle yazardın — çok boilerplate. RTK bunu createSlice ile tek fonksiyona indirir, immutable update'i Immer ile otomatikleştirir. 2025'te "Redux" denince artık pratikte herkes RTK'yı kastediyor.Akış: dispatch → reducer → store → UI
| Adım | Ne olur |
|---|---|
| 1. Dispatch | dispatch(addItem({id:"p1",...})) — component bir "bu oldu" olayı gönderir |
| 2. Reducer | Store, ilgili slice'ın reducer'ını mevcut state + action ile çağırır, yeni state üretir |
| 3. Store günceller | Yeni state store içinde saklanır, tüm subscriber'lara haber verilir |
| 4. UI yeniden render | useSelector ile o state'i okuyan component'ler otomatik güncellenir |
React Query — sunucu verisi için farklı bir araç
Redux'ta sunucudan gelen veriyi de state'e koyup elle loading/error/cache/refetch yönetmeye çalışmak — bugün "anti-pattern" sayılıyor. React Query bunu senin yerine yapar: cache, arka planda yeniden çekme, retry, invalidation.
// React Query (TanStack Query) — SUNUCU verisini yönetir, client state değil
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
function KampanyaListesi() {
const { data, isLoading, error } = useQuery({
queryKey: ["kampanyalar"],
queryFn: () => fetch("/api/kampanyalar").then(r => r.json()),
staleTime: 60_000, // 1 dk boyunca "taze" say, tekrar fetch etme
});
const qc = useQueryClient();
const { mutate: kampanyaEkle } = useMutation({
mutationFn: (yeni) => fetch("/api/kampanyalar", { method: "POST", body: JSON.stringify(yeni) }),
onSuccess: () => qc.invalidateQueries({ queryKey: ["kampanyalar"] }), // cache'i geçersiz kıl → yeniden çek
});
if (isLoading) return <p>Yükleniyor…</p>;
if (error) return <p>Hata oluştu.</p>;
return <ul>{data.map(k => <li key={k.id}>{k.ad}</li>)}</ul>;
}| Redux Toolkit | React Query |
|---|---|
| Client state: sepet, form, tema, modal açık/kapalı | Server state: API'den gelen liste, detay, kullanıcı profili |
| Sen ne zaman güncelleneceğine karar verirsin | Kütüphane otomatik cache'ler, stale olunca kendi yeniler |
| Senkron, tahmin edilebilir | Asenkron, network'e bağlı |
Egzersizler
1) Az önceki demoya 'toplam ürün sayısını sıfırlamadan sepeti %10 indirimli göster' özelliği eklesen, bunu slice'a mı, component'e mi (selector ile hesaplanan türetilmiş değer) koyardın? Neden?
useSelector içinde hesapla. Aksi halde state ile türetilmiş değer birbirinden bağımsız kayıp senkronizasyon bug'larına açık hale gelir.2) createSlice içindeki reducer'da state.items.push(...) yazmak Redux'un 'immutability' kuralını çiğnemiyor mu?
produce fonksiyonuyla sarmalar. Sen bir "draft" obje üzerinde mutate ediyormuş gibi yazarsın, Immer arkada gerçek immutable güncellemeyi üretir. Düz Redux'ta bunu elle {...state, items:[...state.items, yeni]} şeklinde yapman gerekirdi.