React durum yönetimini basitleştirin: sunucu durumunu istemci durumundan ayırın, birkaç kurala uyun ve artan karmaşıklığın erken işaretlerini tespit edin.
Durum, uygulamanız çalışırken değişebilen herhangi bir veridir. Bu, görünen şeyleri (bir modalin açık olması), düzenlediğiniz şeyi (bir form taslağı) ve çektiğiniz verileri (projeler listesi) içerir. Sorun şu ki, bunların hepsi "durum" olarak adlandırılıyor ama çok farklı şekilde davranıyorlar.
Çoğu karmaşık uygulama aynı şekilde bozulur: çok fazla durum türü aynı yerde karışır. Bir bileşen sunucu verisini, UI bayraklarını, form taslaklarını ve türetilmiş değerleri tutar, sonra bunları efektlerle senkronize etmeye çalışır. Kısa süre içinde, "bu değerin kaynağı neresi?" veya "neyi güncelliyor?" gibi basit sorulara birkaç dosyada gezinmeden cevap veremezsiniz.
Üretilen React uygulamaları bu duruma daha hızlı kayma eğilimindedir çünkü ilk çalışan sürümü kabul etmek kolaydır. Yeni bir ekran eklersiniz, bir kalıp kopyalarsınız, bir hatayı başka bir useEffect ile yamalarsınız ve şimdi iki gerçeklik kaynağınız olur. Eğer jeneratör veya ekip yol değiştirse (yerel durum burada, global store orada), kod tabanı tek bir yaklaşıma inşa etmek yerine farklı kalıpları biriktirir.
Hedef sıkıcı: daha az durum türü ve daha az bakılacak yer. Sunucu verisi için bir açık adres ve yalnızca UI'ya ait durum için başka bir açık adres olduğunda, hatalar küçülür ve değişiklikler riskli hissettirmez.
"Sıkıcı tut" demek şu birkaç kurala bağlı kalmak demektir:
Somut bir örnek: bir kullanıcı listesi backend'den geliyorsa, onu sunucu durumu olarak ele alın ve kullanıldığı yerde fetch edin. selectedUserId yalnızca bir detay panelini tetiklemek için varsa, onu o panelin yakınında küçük bir UI durumu olarak tutun. Bu ikisini karıştırmak karmaşıklığın başladığı yoldur.
Çoğu React durum problemi tek bir karışıklıkla başlar: sunucu verisini UI durumu gibi ele almak. Onları erken ayırın, böylece durum yönetimi uygulamanız büyüdükçe sakin kalır.
Sunucu durumu backend'e aittir: kullanıcılar, siparişler, görevler, izinler, fiyatlar, feature flag'ler. Başka bir sekme güncelleyebilir, bir admin düzenleyebilir, bir görev çalışabilir ya da veri süresi dolabilir — uygulamanız hiçbir şey yapmadan değişebilir. Paylaşılan ve değişebilir olduğu için fetch, cache, refetch ve hata yönetimi gerekir.
İstemci durumu ise sadece UI'nizin şu anda umursadığı şeydir: hangi modalin açık olduğu, hangi sekmenin seçili olduğu, bir filtre togglesı, sıralama düzeni, daraltılmış bir kenar çubuğu, kaydedilmemiş arama taslağı. Sekmeyi kapatırsanız kaybetmek sorun değilse, bu istemci durumudur.
Hızlı bir test: "Bu sayfayı yenilesem ve sunucudan yeniden oluştursam olur mu?"
Ayrıca türetilmiş durum vardır; bu, ekstra durum oluşturmaktan sizi kurtarır. Türetilebilen bir değerdir, yani saklamak yerine hesaplayabilirsiniz. Filtrelenmiş listeler, toplamlar, isFormValid ve "boş durum göster" genellikle buraya girer.
Örnek: bir projeler listesi çekersiniz (sunucu durumu). Seçili filtre ve "Yeni proje" iletişim kutusunun açık olma bayrağı istemci durumudur. Filtreleme sonrası görünen liste ise türetilmiş durumdur. Görünen listeyi ayrı bir yerde saklarsanız, senkronizasyon kayar ve "neden eski?" hatalarının peşinden koşarsınız.
Bu ayrım, Koder.ai gibi araçların ekranları hızlıca üretirken işe yarar: backend verisini tek bir fetch katmanında tutun, UI tercihlerine bileşenlere yakın kalın ve hesaplanmış değerleri saklamaktan kaçının.
Durum, bir veri parçasının iki sahibi olduğunda acı verir. İşleri basit tutmanın en hızlı yolu kimin neye sahip olduğunu kararlaştırıp buna sadık kalmaktır.
Örnek: bir kullanıcı listesi çekip birinin detayını gösteriyorsunuz. Yaygın hata, seçili kullanıcı nesnesinin tümünü durumda saklamaktır. Bunun yerine selectedUserId saklayın. Liste sunucu cache'inde kalsın. Detay görünümü ID ile kullanıcıyı bulur; böylece refetchler ekstra senkronizasyon koduna gerek kalmadan UI'yi günceller.
Üretilen React uygulamalarında, bazen jeneratörün "yardımcı" olarak oluşturduğu, sunucu verilerini çoğaltan state'leri kabul etmek kolaydır. Kodda fetch -> setState -> edit -> refetch gibi akışlar görürseniz duraksayın. Bu genellikle tarayıcıda ikinci bir veritabanı kurduğunuzun işaretidir.
Sunucu durumu backend'de yaşayan her şeydir: listeler, detay sayfaları, arama sonuçları, izinler, sayımlar. Sıkıcı yaklaşım bir araç seçip ona bağlı kalmaktır. Çoğu React uygulaması için TanStack Query yeterlidir.
Hedef açık: bileşenler veriyi ister, yüklenme ve hata durumlarını gösterir ve alttaki fetch sayısının kaç olduğuyla ilgilenmez. Bu, üretilen uygulamalarda önemlidir çünkü küçük tutarsızlıklar yeni ekranlar eklendikçe hızla çoğalır.
Query anahtarlarını isimlendirme sistemi gibi düşünün, sonradan akla gelmiş bir şey değil. Onları tutarlı tutun: sabit dizi anahtarları, sonucu değiştiren girdiler (filtreler, sayfa, sıralama) dışında bir şey koymayın ve çok sayıda tekil anahtar yerine birkaç öngörülebilir şekli tercih edin. Birçok ekip anahtar oluşturmayı küçük yardımcı fonksiyonlara koyar, böylece her ekran aynı kuralları kullanır.
Yazmalar için mutation'ları açık başarı işlemleriyle kullanın. Bir mutation iki soruya cevap vermeli: ne değişti ve UI bundan sonra ne yapmalı?
Örnek: yeni bir görev oluşturuyorsunuz. Başarı halinde ya görevler listesinin sorgusunu invalidata edin (böylece bir kez tekrar yüklenir) ya da hedeflenmiş cache güncellemesi yapın (yeni görevi cached listeye ekleyin). Her özellik için bir yaklaşım seçin ve tutarlı kalın.
Eğer birden fazla yerde "her ihtimale karşı" refetch çağrısı eklemek istiyorsanız, tek bir sıkıcı hamle seçin:
İstemci durumu tarayıcının sahip olduğu şeylerdir: bir kenar çubuğunun açık olma bayrağı, seçili satır, filtre metni, kaydedilmemiş bir taslak. Kullanıldığı yere yakın tutarsanız genellikle yönetilebilir kalır.
Küçük başlayın: en yakın bileşende useState kullanın. Ekranları üretirken (örneğin Koder.ai ile) her şeyi global store'a koymak cazip gelebilir — böylece kimsenin anlamadığı bir store ile karşılaşırsınız.
Durumu yalnızca paylaşım sorununu isimlendirebildiğinizde yukarı taşıyın.
Örnek: bir tablo ve detay paneli selectedRowId değerini tabloda tutabilir. Eğer sayfanın başka bir yerindeki bir toolbar da buna ihtiyaç duyuyorsa, sayfa bileşenine yükseltin. Eğer ayrı bir rota (örneğin toplu düzenleme) buna ihtiyaç duyuyorsa, küçük bir store mantıklı olabilir.
Bir store (Zustand veya benzeri) kullanırsanız, tek bir işe odaklı tutun. Store'da "ne"yi saklayın (seçili ID'ler, filtreler), "sonuçlar"ı değil (sıralanmış listeler) — bunları türetin.
Bir store büyümeye başladığında sorun: Bu hâlâ bir özellik mi? Cevap "biraz" ise şimdi bölün, bir sonraki özellik onu dokunmaktan korkacağınız bir durum topuna dönüştürmeden önce.
Form hataları genellikle üç şeyi karıştırmaktan gelir: kullanıcının yazdığı şey, sunucunun kaydettiği ve UI'nın gösterdiği.
Sıkıcı durum yönetimi için formu gönderene kadar istemci durumu olarak ele alın. Sunucu verisi en son kaydedilmiş versiyondur. Form bir taslaktır. Sunucu nesnesini yerinde düzenlemeyin. Değerleri taslağa kopyalayın, kullanıcı özgürce değiştirsin, sonra kaydet ve başarı halinde refetch veya cache güncellemesi yapın.
Kullanıcı sayfadan ayrıldığında neyin kalıp kalmayacağını erkenden belirleyin. Bu tek seçim çok sürpriz hatayı önler. Örneğin, inline edit modu ve açık dropdown'lar genellikle sıfırlanmalı, uzun bir sihirbaz taslağı veya gönderilmemiş bir mesaj taslağı ise kalıcı olabilir. Yenileme sonrası kalıcı olması ancak kullanıcıların bunu açıkça beklediği durumlarda olmalı (ör. bir ödeme formu).
Doğrulama kurallarını tek bir yerde tutun. Kuralları input'lara, submit handler'larına ve yardımcı fonksiyonlara dağıtırsanız, eşleşmeyen hatalarla sonuçlanırsınız. Bir şema (veya tek bir validate() fonksiyonu) tercih edin ve UI hangi durumda hataları göstereceğine karar versin (değişiklikte, blur'da veya submit'te).
Örnek: Koder.ai ile bir Profil Düzenle ekranı üretiyorsunuz. Kaydedilmiş profili sunucu durumu olarak yükleyin. Form alanları için bir taslak oluşturun. "Kaydedilmemiş değişiklikler"i taslak ile kaydedilmişi karşılaştırarak gösterin. Kullanıcı iptal ederse taslağı atın ve sunucu versiyonunu gösterin. Kaydederse taslağı gönderin ve başarılıysa sunucu yanıtıyla kaydedilmiş versiyonu değiştirin.
Üretilen bir React uygulaması büyüdükçe, aynı verinin üç yerde olması yaygındır: bileşen durumu, global store ve cache. Çözüm genellikle yeni bir kütüphane değil; her veri parçası için bir ev seçmektir.
Çoğu uygulamada işe yarayan temizleme akışı:
filteredUsers gibi bir durumu users + filter'dan hesaplayabiliyorsanız kaldırın. Kopyalanmış selectedUser nesnesi yerine selectedUserId tercih edin.Örnek: Bir Koder.ai tarafından üretilen CRUD uygulaması genellikle useEffect ile fetch ve global store kopyası ile başlar. Sunucu durumunu merkezileştirdikten sonra liste tek bir query'den gelir ve "yenile" manuel senkronizasyon yerine invalidation olur.
Adlandırma için tutarlı ve sıkıcı kalın:
users.list, users.detail(id)ui.isCreateModalOpen, filters.userSearchopenCreateModal(), setUserSearch(value)users.create, users.update, users.deleteHedef, her şey için bir gerçeklik kaynağı ve sunucu/istemci durumu arasında net sınırlar olmaktır.
Durum problemleri küçük başlar, sonra bir gün bir alanı değiştirdiğinizde UI'nın üç parçası "gerçek" değerde anlaşmazlığa düşer.
En net uyarı işareti yeniden çoğaltılmış veridir: aynı kullanıcı veya sepet bir bileşende, global store'da ve bir request cache'te yaşıyorsa. Her kopya farklı zamanda güncellenir ve onları eşitlemek için daha fazla kod eklersiniz.
Bir diğer işaret senkronizasyon kodudur: durumu ileri geri iten efektler. "query değiştiğinde store'u güncelle" ve "store değiştiğinde refetch et" gibi kalıplar kenar durumuna kadar çalışabilir, sonra bir kenar vaka eski veya döngüsel değerlere yol açar.
Hızlı birkaç kırmızı bayrak:
needsRefresh, didInit, isSaving gibi paylaşılan bayraklar ekliyorsa.Örnek: Koder.ai ile bir gösterge paneli üretiyorsunuz ve bir Profil Düzenle modalı ekliyorsunuz. Profil verisi query cache'te saklanıyor, global store'a kopyalanıyor ve yerel form durumunda çoğaltılıyorsa üç gerçeklik kaynağınız olur. Arka plan refetching veya iyimser güncellemeler eklediğinizde tutarsızlıklar ortaya çıkar.
Bu işaretleri gördüğünüzde, sıkıcı hamle her veri parçası için tek bir sahip seçmek ve aynaları silmektir.
"Olur da lazım olur" diye bir şeyi saklamak, özellikle üretilen uygulamalarda durumu hızlıca acı verici hale getirir.
API yanıtlarını global store'a kopyalamak yaygın bir tuzaktır. Veri sunucudan geliyorsa (listeler, detaylar, kullanıcı profili), varsayılan olarak istemci store'una kopyalamayın. Sunucu verisi için bir ev seçin (genellikle query cache). İstemci store'u sunucunun bilmediği UI değerleri için kullanın.
Türettirilmiş değerleri saklamak başka bir tuzaktır. Sayılar, filtrelenmiş listeler, toplamlar, canSubmit ve isEmpty genellikle girdilerden hesaplanmalıdır. Performans gerçekten sorun olursa, önce memoize etmeyi veya daha iyi veri yapılarını tercih edin; sonucu saklayarak başlamayın.
Her şeyi tek bir mega-store'da toplamak (auth, modallar, toast'lar, filtreler, taslaklar, onboarding flag'leri) çöplük haline getirir. Özelliğe göre ayırın. Bir state sadece bir ekran tarafından kullanılıyorsa lokal tutun.
Context, sabit değerler (tema, mevcut kullanıcı id'si, locale) için iyidir. Hızla değişen değerler için geniş yeniden renderlara neden olabilir. Bağlantı için Context kullanın; sık değişen UI değerleri için component state veya küçük bir store kullanın.
Son olarak, tutarsız adlandırmadan kaçının. Neredeyse aynı query anahtarları ve store alanları ince çoğaltmalara yol açar. Basit bir standart seçin ve ona uyun.
"Sadece bir tane daha" durum değişkeni ekleme isteği geldiğinde, hızlı bir sahiplik kontrolü yapın.
İlk olarak, sunucu fetch ve cache'inin tek bir yerini (tek bir query aracı, tek bir query anahtar seti) gösterebiliyor musunuz? Aynı veri birden fazla bileşende fetch ediliyor ve ayrıca bir store'a kopyalanıyorsa zaten faiz ödüyorsunuz demektir.
İkincisi, bu değer sadece bir ekran içinde mi gerekli (ör. "filtre paneli açık mı")? Öyleyse global olmamalı.
Üçüncüsü, bir nesnenin tamamını çoğaltmak yerine ID saklayabilir misiniz? selectedUserId saklayın ve kullanıcıyı cache'den veya listeden okuyun.
Dördüncüsü, bu türetilmiş mi? Mevcut durumdan hesaplayabiliyorsanız saklamayın.
Son olarak, bir dakikalık iz sürme testi yapın. Bir ekip üyesi bir dakika içinde "bu değer nereden geliyor?" (prop, lokal durum, server cache, URL, store) sorusuna cevap veremiyorsa, daha fazla durum eklemeden önce sahipliği düzeltin.
Bir jeneratörden (örneğin Koder.ai tarafından bir prompt ile üretilen) admin uygulaması hayal edin: müşteri listesi, müşteri detay sayfası ve bir düzenleme formu.
Durumlar belirgin evlere sahip olduğunda sakin kalır:
Liste ve detay sayfaları server state'i query cache'ten okur. Kaydettiğinizde müşterileri global store'a tekrar yazmazsınız. Mutation gönderirsiniz, sonra cache'in yenilenmesine veya güncellenmesine izin verirsiniz.
Düzenleme ekranı için form taslağını lokal tutun. Fetched müşteriden başlatın ama kullanıcı yazmaya başladıktan sonra ayrı kabul edin. Böylece detay görünümü güvenle yenilenebilir, yarım kalmış değişiklikler üzerine yazılmaz.
İyimser UI genellikle takımların her şeyi çoğaltmasına neden olur. Çoğu durumda buna gerek yoktur.
Kullanıcı Kaydet dediğinde, yalnızca cached müşteri kaydını ve eşleşen liste öğesini güncelleyin, sonra istek başarısız olursa geri alın. Taslak formda kalmaya devam etsin; başarısız olursa hata gösterin ve kullanıcı yeniden denesin.
Toplu düzenleme gibi bir özellik seçili satırları da istiyorsa, yeni bir store oluşturmadan önce sorun: bu durum navigasyon ve yenileme sırasında korunmalı mı?
Eğer evet ise, URL'e koyun (seçili ID'ler, filtreler). Eğer hayır ise, sayfa bileşeninde tutun. Birden fazla uzak bileşen gerçekten aynı anda ihtiyaç duyuyorsa (toolbar + tablo + footer), sadece o istemci durumu için küçük bir paylaşılan store ekleyin.
Üretilen ekranlar hızlıca çoğalabilir ve bu harika, ta ki her yeni ekran kendi durum kararlarını getirene kadar.
Depoya kısa bir ekip notu yazın: sunucu durumunun ne sayıldığı, istemci durumunun ne sayıldığı ve her birine hangi aracın sahip olduğu. Yeterince kısa olsun ki insanlar gerçekten uygulasın.
Küçük bir pull request alışkanlığı ekleyin: her yeni durum parçasını server veya client olarak etiketleyin. Eğer server durumuysa, "nerede yükleniyor, nasıl cacheleniyor ve neyle invalidata ediliyor?" diye sorun. Eğer client durumuysa, "kimin sahibi ve ne zaman sıfırlanıyor?" diye sorun.
Koder.ai kullanıyorsanız (koder.ai), Planning Mode yeni ekranları üretmeden önce sahipliği kararlaştırmanıza yardımcı olabilir. Snapshot ve rollback, bir durum değişikliği ters giderse geri dönmenin güvenli bir yolunu sağlar.
Bir özelliği seçin (örneğin profil düzenleme), kuralları uçtan uca uygulayın ve bunun ekip için örnek olmasına izin verin.
Start by labeling every piece of state as server, client (UI), or derived.
isValid).Once you label them, make sure each item has one obvious owner (query cache, local component state, URL, or a small store).
Use this quick test: “Could I refresh the page and rebuild this from the server?”
Example: a project list is server state; the selected row ID is client state.
Because it creates two sources of truth.
If you fetch users and then copy them into useState or a global store, you now have to keep them in sync during:
Default rule: and only create local state for UI-only concerns or drafts.
Store derived values only when you truly can’t compute them cheaply.
Usually you should compute from existing inputs:
visibleUsers = users.filter(...)total = items.reduce(...)canSubmit = isValid && !isSavingIf performance becomes real (measured), prefer or better data structures before introducing more stored state that can go stale.
Default: use a server-state tool (commonly TanStack Query) so components can just “ask for data” and handle loading/error states.
Practical basics:
Keep it local until you can name a real sharing need.
Promotion rule:
This keeps your global store from becoming a dumping ground for random UI flags.
Store IDs and small flags, not full server objects.
Example:
selectedUserIdselectedUser (copied object)Then render details by looking up the user from the cached list/detail query. This makes background refetches and updates behave correctly without extra syncing effects.
Treat the form as a draft (client state) until you submit.
A practical pattern:
This avoids accidentally editing server data “in place” and fighting refetches.
Common red flags:
needsRefresh, didInit, isSaving that keep accumulating.Generated screens can drift into mixed patterns fast. A simple safeguard is to standardize ownership:
If you’re using Koder.ai, use Planning Mode to decide ownership before generating new screens, and rely on snapshots/rollback when experimenting with state changes so you can back out cleanly if a pattern goes wrong.
useMemoAvoid sprinkling refetch() calls everywhere “just in case.”
The fix is usually not a new library—it’s deleting mirrors and picking one owner per value.