ERETEK
BİLGİ SİSTEMLERİ

React’ta Durum Yönetimi: Sunucu Durumunu İstemci Durumundan Ayırmak

React state yönetiminde sunucu, URL, form ve istemci durumunu ayırın; TanStack Query, Context, Zustand ve Redux Toolkit seçimi ve şişmiş store’dan çıkış sırası.

Bu makalede7 bölüm
Teknik özet
Teknoloji
React, TanStack Query, SWR, Zustand, Redux Toolkit, React Router
Konu
React uygulamasında durum türlerini ayırmak ve araç seçimi
Seviye
Orta–ileri
Ön koşullar
  • React hook’ları (useState, useEffect) bilgisi
  • REST veya GraphQL API ile çalışma deneyimi

Şişmiş bir Redux store’un içini açtığınızda gördüğünüz şey çoğu zaman istemci durumu değildir: sipariş listeleri, kullanıcı kayıtları, isLoading bayrakları, elle tutulan lastFetched zaman damgaları. Bunların hepsi sunucuda yaşayan verinin kopyasıdır ve store onları tazelemek, tutarlı tutmak için kod yığınına dönüşür. “Hangi state kütüphanesini seçelim?” sorusu bu yüzden yanlış yerden sorulur. Önce durumu türlerine ayırın, araç seçimi arkasından gelir.

Durum türlerini ayırmak

Kütüphane tartışmasına girmeden uygulamadaki her durum parçasını aşağıdaki dört sınıftan birine yerleştirin. Bu ayrım, hangi aracı seçtiğinizden daha belirleyicidir.

Tür Gerçek sahibi Örnek Uygun araç
Sunucu durumu Backend / API Sipariş listesi, kullanıcı profili TanStack Query, SWR
URL durumu Adres çubuğu Filtre, sayfa no, sıralama, seçili sekme Router arama parametreleri
Form durumu Form bileşeni Düzenlenen alanlar, doğrulama hataları React Hook Form, yerel state
İstemci (UI) durumu Tarayıcı oturumu Açık modal, tema, sepet taslağı useState, Context, Zustand, Redux Toolkit

Sunucu durumu diğer üçünden temelden farklıdır: başkası değiştirebilir, siz ekrana bakarken eskiyebilir ve asenkron gelir. Onu bir değişken gibi değil, uzak bir kaynağın önbelleği gibi yönetirsiniz. Bir değerin sınıfını bulmak için tek soru yeter: “Sayfayı yenilediğimde bu değer nereden geri gelmeli?” Cevap API ise sunucu durumu, adres çubuğuysa URL durumu, hiçbir yerden gelmemesi gerekiyorsa istemci durumudur. Bu soruyu store’daki her dilime sorun. Tablo kendiliğinden dolar.

Sunucu durumu ve veri önbellekleme kütüphaneleri

TanStack Query ve SWR sunucu verisini bir anahtar (query key) altında önbelleğe alır. Yükleniyor ve hata durumlarını, arka planda yeniden doğrulamayı, aynı anda giden istekleri birleştirmeyi (deduplication) ve değişiklik sonrası geçersiz kılmayı (invalidation) sizin yerinize yönetir. Redux içinde isLoading, error, lastFetched alanlarını elle tutan kodun önemli kısmı bu kütüphanelerle ortadan kalkar.

// TanStack Query v5 ile sipariş listesi ve güncelleme
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

export function useOrders(filters: { status: string; page: number }) {
  return useQuery({
    queryKey: ['orders', filters],          // filtre anahtarın parçasıdır
    queryFn: () => api.get('/orders', { params: filters }),
    staleTime: 30_000,                       // 30 sn boyunca taze kabul et
  });
}

export function useApproveOrder() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) => api.post(`/orders/${id}/approve`),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['orders'] }),
  });
}

Bu kodda üç ayar işi belirler. Query key tasarımı: filtreler anahtara girmezse farklı filtrelerin sonuçları birbirinin üzerine yazılır; kullanıcı “Onaylı” sekmesine geçer ama bir an “Açık” siparişleri görür. staleTime: varsayılan sıfırdır, veri hemen “bayat” sayılır ve bileşen yeniden bağlandığında, pencere odağı döndüğünde veya ağ bağlantısı yeniden kurulduğunda arka planda tekrar çekilir. Ülke listesi, ürün kategorileri gibi seyrek değişen referans verilerde bu değeri yükseltmek gereksiz istekleri keser. Geçersiz kılma kapsamı: ['orders'] önekiyle geçersiz kılmak tüm filtre kombinasyonlarını yeniler; daha dar anahtar vermek mümkündür ama tutarsızlık riskini artırır.

Next.js App Router projelerinde verinin bir kısmı Server Component içinde çekilir ve istemciye hiç state olarak gelmez. Bu durumda istemci tarafı sorgu kütüphanesi yalnız etkileşimli bölümlerde gerekir: sonsuz kaydırma, canlı filtre, mutasyon sonrası yenileme. Sınırın nereden çizileceği Next.js App Router’da Server ve Client Component ayrımı rehberinde ayrıntılı anlatılıyor.

İyimser güncelleme ne zaman kullanılmalı?

Onay düğmesi, yıldızlama, sıralama değiştirme gibi anında geri bildirim beklenen işlemlerde iyimser güncelleme (optimistic update) arayüzü sunucu yanıtını beklemeden günceller, hata dönerse geri alır. Tutarın, stokun veya yetkinin değiştiği işlemlerde sunucu yanıtını bekleyin. Kullanıcıya “onaylandı” gösterip iki saniye sonra geri almak iş süreçlerinde güveni sarsar. Para işinde iyimserlik olmaz.

URL ve form durumu

Kurumsal iş uygulamalarının liste ekranlarında filtre, sayfa numarası ve sıralama global store’a konunca üç şikâyet gelir: paylaşılan bağlantı aynı görünümü açmaz, tarayıcının geri tuşu beklendiği gibi çalışmaz, sayfa yenilenince kullanıcı baştan başlar. Destek ekibine düşen “linki gönderdim ama karşı taraf başka liste görüyor” bildiriminin kaynağı genellikle budur. Bu bilgilerin doğal yeri URL’dir.

// React Router v6: filtreyi URL'de tutmak
// (v7'de aynı kanca 'react-router' paketinden de içe aktarılabilir)
import { useSearchParams } from 'react-router-dom';

function OrderFilters() {
  const [params, setParams] = useSearchParams();
  const status = params.get('status') ?? 'open';
  const page = Number(params.get('page') ?? '1');

  const { data } = useOrders({ status, page }); // liste ayrı bileşende render edilir

  return (
    <select
      value={status}
      onChange={(e) => setParams({ status: e.target.value, page: '1' })}
    >
      <option value="open">Açık</option>
      <option value="approved">Onaylı</option>
    </select>
  );
}

URL parametreleri metindir. Sayıya, tarihe veya listeye çevirirken geçersiz değeri varsayılana düşürün; elle yazılmış ?page=abc ekranı çökertmemeli. Next.js’te aynı desen useSearchParams ve useRouter ile, App Router’da ayrıca sayfa bileşeninin searchParams özelliğiyle sunucu tarafında kurulur (Next.js 15 ve sonrasında bu özellik bir Promise olarak gelir ve await edilmesi gerekir).

Form durumu kısa ömürlüdür: form açıkken vardır, gönderildikten sonra anlamını yitirir. Her tuş vuruşunu global store’a yazmak gereksiz yeniden render üretir, birden fazla formun açık olduğu ekranlarda da çakışma yaratır. React Hook Form gibi kütüphaneler alan değerlerini bileşen içinde tutar; sunucuya gönderim bir mutasyondur ve sunucu durumu katmanına aittir. Doğrulama kurallarını istemci ve sunucuda ortaklaştırmak form doğrulamasını tek şemayla yönetmek rehberinin konusu.

Global istemci durumu seçenekleri

Üç tür ayrıldıktan sonra geriye kalan gerçek istemci durumu genellikle küçüktür: kullanıcının arayüz tercihleri, tema, açık kenar çubuğu, çok adımlı sihirbazın taslağı, yerel sepet. Seçenekler şunlar:

  • useState / useReducer ve durumu yukarı taşımak: Durumu birkaç bileşen kullanıyorsa ilk tercih budur. Karmaşık geçişler varsa useReducer mantığı test edilebilir kılar.
  • Context: Bir değeri ağaç boyunca prop geçirmeden dağıtma mekanizmasıdır, state yönetim kütüphanesi değildir. Context değeri değişince onu okuyan tüm bileşenler yeniden render edilir. Tema, dil, oturum bilgisi gibi seyrek değişen değerlerde uygundur; sık değişen değerde performans sorunu çıkarır.
  • Zustand: Küçük API’li, provider gerektirmeyen bir store. Bileşenler seçici (selector) ile yalnız ihtiyaç duydukları parçaya abone olur, ilgisiz değişiklikte render tetiklenmez.
  • Redux Toolkit: Katı tek yönlü veri akışı, Redux DevTools ile zaman yolculuğu, middleware ekosistemi ve RTK Query ile entegre sunucu durumu yönetimi sunar. Büyük ekipte öngörülebilirlik ve denetlenebilirlik verir; karşılığında daha fazla yapı ister.
// Zustand: seçiciyle abone olmak
import { create } from 'zustand';

type UiState = { sidebarOpen: boolean; toggleSidebar: () => void };

export const useUi = create<UiState>()((set) => ({
  sidebarOpen: true,
  toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));

// Bileşen yalnızca sidebarOpen değişince render olur
const open = useUi((s) => s.sidebarOpen);

Kütüphane karşılaştırma tablosu

Ölçüt Context Zustand Redux Toolkit
Ek bağımlılık Yok Küçük Orta (RTK + react-redux)
Seçici ile kısmi abonelik Yok (Context bölünerek) Var Var (useSelector)
DevTools / iz sürme React DevTools Middleware ile Redux DevTools Yerleşik
Sunucu durumu desteği Yok Yok (Query ile birlikte) RTK Query
Uygun ölçek Seyrek değişen birkaç değer Küçük–orta istemci durumu Büyük ekip, karmaşık iş kuralları

Karar sırası pratikte şöyle işler: önce durumun sınıfını belirleyin; sunucu durumunu sorgu kütüphanesine, URL durumunu router’a verin. Kalan istemci durumu tek ekranın içindeyse yerel state yeter. Uygulama genelinde ve seyrek değişiyorsa Context, sık değişiyor ve çok bileşen okuyorsa Zustand uygundur. Ekip Redux Toolkit’e zaten yatırım yapmışsa ve RTK Query ile sunucu durumunu aynı çatıda tutmak istiyorsa, kütüphane değiştirmek yerine mevcut yapıyı sadeleştirmek daha ucuza gelir. Yeniden yazım genellikle en pahalı yoldur.

API tarafındaki seçimin istemci önbelleğine etkisi için REST mi GraphQL mi karşılaştırmasına bakın; GraphQL istemcileri (ör. Apollo Client) kendi normalleştirilmiş önbelleğini getirdiği için ayrı bir sorgu kütüphanesine gerek kalmayabilir.

Aşırı global durumdan kurtulma adımları

Yıllar içinde büyümüş bir kurumsal web uygulamasının Redux store’unu tek seferde yeniden yazmak, canlı sistemde çekirdek switch’i mesai saatinde değiştirmeye benzer. Aşağıdaki sıra özelliği bozmadan ve geri dönüş yolunu açık tutarak ilerler.

  1. Envanter çıkarın. Store’daki her dilimi (slice) listeleyin ve dört sınıftan birine atayın. Çoğu projede listelerin, detay kayıtlarının ve yükleniyor bayraklarının sunucu durumu olduğu çıkar.
  2. Sorgu kütüphanesini yanına kurun. TanStack Query’nin QueryClientProvider’ını Redux Provider’ı ile birlikte ekleyin. İki sistem bir süre yan yana çalışabilir.
  3. Okuma uçlarını tek tek taşıyın. En basit liste ekranından başlayın: thunk + reducer + selector üçlüsünü bir useQuery kancasıyla değiştirin. Dilimi silmeden önce başka bir yerin onu okumadığını kod aramasıyla doğrulayın.
  4. Mutasyonları taşıyın. Kaydetme ve onaylama işlemlerini useMutation’a alın; başarı sonrası store’u elle güncellemek yerine ilgili sorguları geçersiz kılın.
  5. Filtreleri URL’ye çıkarın. Store’daki filtre ve sayfalama bilgisini arama parametrelerine taşıyın; derin bağlantı ve geri tuşu davranışı kendiliğinden düzelir.
  6. Kalan istemci durumunu küçültün. Geriye birkaç UI dilimi kaldıysa Redux’ta bırakmak ya da Zustand’a taşımak ekip tercihidir. Hedef kütüphane değil, sade bir durum modelidir.

Sık yapılan hatalar

  • Sunucu verisini kopyalamak: Sahada en sık gördüğümüz hata budur. useQuery sonucunu useEffect içinde ayrı bir state’e yazmak iki gerçeklik kaynağı yaratır ve biri mutlaka geride kalır. Türetilmiş değeri render sırasında hesaplayın veya select seçeneğini kullanın.
  • Türetilebilen değeri saklamak: Toplam tutar, filtrelenmiş liste, seçili öğe sayısı başka state’ten hesaplanabiliyorsa ayrıca tutulmaz.
  • Tek dev Context: Kullanıcı, tema, bildirim ve sepeti aynı Context değerine koymak, birinin değişmesiyle tüm uygulamanın yeniden render olmasına yol açar. Context’leri sorumluluğa göre bölün.
  • Query key’de eksik parametre: İstek fonksiyonunun kullandığı her değişken anahtarda yer almalıdır; TanStack Query’nin ESLint eklentisi (@tanstack/eslint-plugin-query) bu hatayı yakalayan bir kural içerir.

Taşıma sürerken ölçmeden ilerlemeyin. React DevTools Profiler ile kritik ekranlarda render sayısını ve süresini önce ve sonra karşılaştırın; tarayıcının ağ sekmesinde aynı uca giden yinelenen istekleri arayın. Aynı uç bir sayfa açılışında üç kez çağrılıyorsa önbellek anahtarı yanlıştır. Durum katmanındaki tip tanımlarının ve klasör sınırlarının ekip genelinde tutarlı kalması için TypeScript kod tabanı standartları rehberindeki modül sınırı yaklaşımını bu çalışmayla birlikte uygulayın.

Mevcut bir React uygulamasının durum katmanını sadeleştirmek veya yeni projede bu ayrımı baştan kurmak için React ve Next.js web uygulamaları kapsamında mimari değerlendirme yapıyoruz. Kavramsal arka plan için React dokümantasyonundaki Managing State bölümü ve TanStack Query dokümantasyonu birincil kaynaklardır.

Sık sorulan sorular

Redux artık gereksiz mi?

Hayır. Sunucu durumu sorgu kütüphanesine taşınınca Redux’ta kalan iş azalır. Karmaşık istemci tarafı iş kuralları, zaman yolculuğuyla hata ayıklama ihtiyacı ve büyük ekipte katı yapı isteyen projelerde Redux Toolkit hâlâ mantıklı bir seçimdir.

TanStack Query ile SWR arasında nasıl seçim yapılır?

İkisi de sunucu durumunu önbelleğe alır ve arka planda yeniden doğrular. SWR daha küçük bir API sunar; TanStack Query mutasyon, geçersiz kılma, sonsuz sorgu ve DevTools tarafında daha kapsamlıdır. Mutasyonun yoğun olduğu iş uygulamalarında TanStack Query genellikle daha az elle kod ister.

Context performans sorunu yaratır mı?

Context değeri değişince onu okuyan tüm bileşenler yeniden render edilir. Tema veya dil gibi seyrek değişen değerlerde sorun çıkmaz. Sık değişen ve çok bileşenin okuduğu değerlerde Context’i bölün ya da seçici destekleyen bir store kullanın.

Filtreleri neden URL’de tutmalıyım?

URL’deki filtre paylaşılan bağlantıda korunur, tarayıcının geri ve ileri tuşlarıyla doğru çalışır, sayfa yenilenince kaybolmaz. Store’da tutulan filtre bu davranışların hiçbirini kendiliğinden sağlamaz.

Teknik özet
Teknoloji
React, TanStack Query, SWR, Zustand, Redux Toolkit, React Router
Konu
React uygulamasında durum türlerini ayırmak ve araç seçimi
Seviye
Orta–ileri
Ön koşullar
  • React hook’ları (useState, useEffect) bilgisi
  • REST veya GraphQL API ile çalışma deneyimi
Eretek uzman desteği

Bu konu sizin ortamınızda nasıl çalışır, birlikte bakalım.

Mevcut yapınızı inceliyor, darboğazı tespit ediyor ve uygulanabilir bir iş planı öneriyoruz.