React durum yönetimi: üretilen uygulamalarda sade kalın
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.

Gerçek React uygulamalarında durumla ne yanlış gidiyor
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:
- Sunucu verisini istemci durumuna aynen kopyalamayın, net bir sebep olmadıkça.
- Yerel UI durumunu sadece gelecekte işinize yarayabilir diye global store'a taşımayın.
- Türeyen değerleri gerçekten pahalı olmadıkça kendi başlarına durum haline getirmeyin.
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.
Sunucu durumu vs istemci durumu, sade bir dille
Ç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?"
- Eğer evet ise, muhtemelen sunucu durumudur.
- Eğer hayır ise, muhtemelen istemci durumudur.
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.
Çoğu sorunu önleyen sıkıcı kurallar seti
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.
- API verisini fetch edip cache'lemekten ziyade bileşen durumunda elle yönetmeyin.
- Çekilen veriyi "belki lazım olur" diye yerel duruma kopyalamayın. Kopyalar kayma yaratır.
- İstemci durumunu kullanıldığı yere yakın tutun. Gerçekten ağaçtaki ayrı parçaların paylaşımına ihtiyaç duyulursa yukarı taşıyın.
- Tam nesneleri saklamak yerine ID'leri ve küçük bayrakları saklayın. Nesneyi render ederken cache'den yeniden türetin.
Ö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 durumunu fazla düşünmeden nasıl ele alırsınız
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:
- Stale olan tam sorgu anahtarını invalidata edin.
- Değişen tam sorgu için cache'i güncelleyin.
- Doğru sorguyu zaten fetch eden bir ekrana yönlendirin.
İstemci durumunda küçük kalan desenler
İ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.
Basit bir yükseltme kuralı
Durumu yalnızca paylaşım sorununu isimlendirebildiğinizde yukarı taşıyın.
- Varsayılan olarak UI'ya özel durumu lokal tutun.
- Kardeşlerin buna ihtiyacı olduğunda en yakın ortak üst bileşene yükseltin.
- Birden fazla rotanın veya uzak bileşenlerin aynı anda aynı değere ihtiyacı olduğunda küçük bir paylaşılan store kullanı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.
Okunabilir kalan store şekilleri
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.
Formlar, taslaklar ve geçici UI durumu
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.
Adım adım: dağınık bir durum kurulumunu sadeleştirmek
Ü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ışı:
- Durumun envanterini çıkarın. Elinizdeki şeyleri listeleyin (listeler, seçili öğe, filtreler, modallar, taslaklar) ve her birini sunucu veya istemci olarak etiketleyin.
- Kopyaları silin.
filteredUsersgibi bir durumuusers+filter'dan hesaplayabiliyorsanız kaldırın. KopyalanmışselectedUsernesnesi yerineselectedUserIdtercih edin. - Fetch işini tek bir katmana koyun. Cache, refetch ve invalidation için tek bir kural kitabı olsun.
- Küçük bir istemci store'u yalnızca işe yaradığı yerde ekleyin (sayfalar arası UI ihtiyaçları gibi).
- Karmaşıklığın geri gelmemesi için adlandırma kurallarını sabitleyin.
Ö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:
- Sorgular:
users.list,users.detail(id) - İstemci durumu:
ui.isCreateModalOpen,filters.userSearch - Eylemler:
openCreateModal(),setUserSearch(value) - Sunucu yazmaları:
users.create,users.update,users.delete
Hedef, her şey için bir gerçeklik kaynağı ve sunucu/istemci durumu arasında net sınırlar olmaktır.
Durum karmaşıklığının patlamak üzere olduğuna dair erken işaretler
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:
- "Bu değerin kaynağı neresi ve kimin sahibi?" sorusunu bir cümlede açıklayamıyorsanız.
- Store veya reducer API client'ları veya HTTP durum kodlarını içe aktarıyorsa.
- Her yeni ekran
needsRefresh,didInit,isSavinggibi paylaşılan bayraklar ekliyorsa. - Durumu birbirine yansıtmak için efektler yazıyorsanız.
- Bir hatayı düzeltmek için aynı alanı birden fazla katmanda güncellemek gerekiyorsa.
Ö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.
Yaygın tuzaklar ve nasıl kaçınılır
"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.
Daha fazla durum eklemeden önce hızlı kontrol listesi
"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.
Örnek: büyürken sakin kalan bir CRUD uygulaması
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:
- Sunucu durumu: müşteri listesi, müşteri kaydı, kaydet/sil istekleri.
- İstemci durumu: liste filtreleri, seçili satır, düzenleme taslağı.
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.
Sıkıcı bir iyimser güncelleme
İ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.
İkinci bir özellik aynı duruma ihtiyaç duyduğunda
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.
Sonraki adımlar: üretilen uygulamalarda kurallarınızı tutarlı kılın
Ü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.
SSS
What’s the simplest way to untangle messy React state?
Start by labeling every piece of state as server, client (UI), or derived.
- Server: fetched data like users, tasks, permissions.
- Client: UI choices like “modal open”, selected tab, filter text.
- Derived: values you can compute (filtered list, totals,
isValid).
Once you label them, make sure each item has one obvious owner (query cache, local component state, URL, or a small store).
How do I tell if something is server state or client state?
Use this quick test: “Could I refresh the page and rebuild this from the server?”
- If yes, treat it as server state (fetch/cache/refetch).
- If no, treat it as client state (local UI state, maybe a store).
Example: a project list is server state; the selected row ID is client state.
Why is copying API data into local or global state such a big problem?
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:
- background refetches
- updates from other tabs/users
- partial updates after mutations
Default rule: read server data from one place (a query cache) and only create local state for UI-only concerns or drafts.
When is it okay to store derived values instead of computing them?
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 && !isSaving
If performance becomes real (measured), prefer useMemo or better data structures before introducing more stored state that can go stale.
What’s a “boring” way to handle server state in React?
Default: use a server-state tool (commonly TanStack Query) so components can just “ask for data” and handle loading/error states.
Practical basics:
- Use stable, consistent query keys (include only inputs that change results).
- For writes, use mutations and pick one strategy per feature:
- invalidate the exact list/detail query, or
- update the cache in a targeted way.
Avoid sprinkling refetch() calls everywhere “just in case.”
When should I move UI state into a global store?
Keep it local until you can name a real sharing need.
Promotion rule:
- Local component state by default.
- Lift to the closest common parent when siblings need it.
- Use a small shared store only when multiple distant components/routes need it at the same time.
This keeps your global store from becoming a dumping ground for random UI flags.
Should I store the selected object or just its ID?
Store IDs and small flags, not full server objects.
Example:
- ✅
selectedUserId - ❌
selectedUser(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.
What’s the cleanest way to manage forms and drafts?
Treat the form as a draft (client state) until you submit.
A practical pattern:
- Fetch the saved record as server state.
- Initialize a local draft from it.
- Let the user edit the draft freely.
- On save, submit the draft and then refresh/update the server cache.
- On cancel, drop the draft and show the server version.
This avoids accidentally editing server data “in place” and fighting refetches.
What are early warning signs that state complexity is about to explode?
Common red flags:
- The same data exists in a component, a global store, and a query cache.
- Effects that mirror state back and forth ("when query changes, set store", "when store changes, refetch").
- Shared flags like
needsRefresh,didInit,isSavingthat keep accumulating. - You can’t explain “where does this value come from?” in one sentence.
The fix is usually not a new library—it’s deleting mirrors and picking one owner per value.
How do I keep state “boring” when using a generator like Koder.ai?
Generated screens can drift into mixed patterns fast. A simple safeguard is to standardize ownership:
- Server state: always via the same fetching/caching layer.
- Client UI state: local by default, store only when needed.
- Derived values: computed, not stored.
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.