Uygulamalar birçok gerçeklik kaynağı, asenkron veri, UI etkileşimi ve performans ödünleriyle uğraştığı için durum yönetimi zordur. Hataları azaltmak için kalıpları öğrenin.

Bir frontend uygulamada durum, basitçe arayüzünüzün bağlı olduğu ve zaman içinde değişebilen veridir.
Durum değiştiğinde ekranın buna uyum sağlaması gerekir. Ekran güncellenmezse, tutarsız güncellenir ya da eski ve yeni değerlerin karışımını gösterirse, “durum problemlerini” hemen hissedersiniz—butonlar kilitli kalır, toplamlar tutmaz veya görünüm kullanıcının yaptığı işlemi yansıtmaz.
Durum hem küçük hem de büyük etkileşimlerde ortaya çıkar, örneğin:
Bunların bazıları “geçici” (ör. seçili sekme) iken bazıları “önemli” hissi verir (ör. sepet). Hepsi durumdur çünkü şu anda UI'ın ne göstereceğini etkilerler.
Basit bir değişken sadece yaşadığı yere bağlıdır. Durum farklıdır çünkü kuralları vardır:
Durum yönetiminin gerçek amacı veri depolamak değil—güncellemeleri öngörülebilir kılmak, böylece UI tutarlı kalır. “Ne değişti, ne zaman ve neden?” sorularına cevap verebildiğinizde durum yönetilebilir olur. Cevap veremediğinizde, basit özellikler bile sürprizlere dönüşür.
Bir frontend projesinin başında durum neredeyse sıkıcı derecede basit gelir—iyi anlamda. Tek bir bileşeniniz, tek bir girişiniz ve tek bir güncellemeniz vardır. Kullanıcı bir alana yazı yazar, siz değeri kaydedersiniz ve UI yeniden render olur. Her şey görünür, anlık ve kapsayıcıdır.
Tek bir metin girişi düşünün, yazdığınızı önizliyor:
Bu kurulumda durum temelde: zaman içinde değişen bir değişkendir. Nerede saklandığını ve nerede güncellendiğini gösterebilirsiniz, hepsi bu.
Yerel durum işe yarar çünkü zihinsel model kod yapısıyla eşleşir:
React gibi bir çerçeve kullanıyor olsanız bile, mimari hakkında derin düşünmenize gerek yoktur. Varsayılanlar çoğu durumda yeterlidir.
Uygulama “bir widget olan bir sayfa” olmaktan çıkıp “bir ürün” haline gelir gelmez, durum tek bir yerde yaşamayı bırakır.
Aynı veri artık şu yerlerde gerekebilir:
Bir profil adı üst bilgide gösterilebilir, ayarlar sayfasında düzenlenebilir, daha hızlı yükleme için önbelleğe alınabilir ve karşılama mesajını kişiselleştirmek için kullanılabilir. Artık soru “bu değeri nasıl saklarım?” değil, “bu değer nerede yaşamalı ki her yerde doğru kalsın?” olur.
Durum karmaşıklığı özelliklerle birlikte kademeli artmaz—ani sıçramalar olur.
Aynı veriyi okuyan ikinci bir yer eklemek “iki kat daha zor” değildir. Koordinasyon sorunları getirir: görünümleri tutarlı tutma, eski değerleri önleme, kimin neyi güncelleyeceğine karar verme ve zamanlamayı ele alma. Birkaç paylaşılan durum parçası ve asenkron işler olduğunda, tek tek her özellik basit gözükse de davranışları anlamak zorlaşır.
Aynı “olgu”nun birden fazla yerde saklandığı durumlarda işler acı verici olur. Her kopya sürüklenebilir ve artık UI kendi içinde çelişir hale gelir.
Çoğu uygulama birkaç yerde “gerçek” tutabilir:
Bunların her biri bazı durumlar için geçerli sahibidir. Sorun, hepsi aynı durumu sahiplenmeye çalıştığında başlar.
Yaygın bir örnek: sunucudan veri alınır, sonra “düzenlemek için” yerel duruma kopyalanır. Örneğin bir kullanıcı profili yüklenir ve formState = userFromApi yapılır. Daha sonra sunucu tekrar sorgulanır (veya başka bir sekmede kayıt güncellenir) ve artık iki versiyonunuz olur: önbellek bir şey söyler, form başka bir şey.
Kopyalanma ayrıca “yardımcı” dönüşümlerle de girer: hem items hem de itemsCount saklamak, ya da hem selectedId hem de selectedItem tutmak gibi.
Birden fazla gerçeklik kaynağı olduğunda hatalar genellikle şöyle seslenir:
Her durum parçası için bir sahip seçin—güncellemelerin yapıldığı yer—ve diğer herkesi projeksiyon (salt okunur, türetilmiş veya tek yönlü senkronize) olarak ele alın. Sahibini işaret edemiyorsanız, muhtemelen aynı gerçeği iki kez saklıyorsunuz.
Birçok frontend durumu basit görünür çünkü eşzamanlıdır: kullanıcı tıklar, siz bir değeri ayarlarsınız, UI güncellenir. Yan etkiler bu düzenli adım-adım hikâyeyi bozar.
Yan etkiler, bileşenin saf “veriye göre render etme” modelinin dışına çıkan her eylemdir:
Her biri daha sonra tetiklenebilir, beklenmedik şekilde başarısız olabilir veya birden çok kez çalışabilir.
Asenkron güncellemeler zaman değişkenini tanıtır. Artık “ne oldu” değil, “hala ne oluyor olabilir” diye düşünürsünüz. İki istek üst üste gelebilir. Yavaş bir yanıt daha yenisinden sonra gelebilir. Bir bileşen unmount olurken asenkron geri çağrı hâlâ durumu güncellemeye çalışabilir.
Bu yüzden hatalar genellikle şöyle görünür:
isLoading gibi boolean’ları her yere saçmak yerine asenkron işi küçük bir durum makinesi gibi ele alın:
Veri ve durum bilgisini birlikte takip edin ve geç kalan yanıtları göz ardı edebilmek için bir tane tanımlayıcı (istek id'si veya sorgu anahtarı) saklayın. Bu, “şu anda UI ne göstermeli?” sorusunu tahminden çıkarır.
Birçok durum sorunu basit bir karışmadan kaynaklanır: “kullanıcının şu an yaptığı” ile “backend'in doğru dediği” aynıymış gibi davranmak. Her ikisi de zamanla değişebilir, ama kuralları farklıdır.
UI durumu geçici ve etkileşim odaklıdır. Ekranı kullanıcının beklentisine uygun şekilde o an göstermek için vardır.
Örnekler: açık/kapalı modal, aktif filtreler, arama girişi taslağı, hover/focus durumları, hangi sekmenin seçili olduğu ve sayfalama UI'ı (geçerli sayfa, sayfa büyüklüğü, kaydırma pozisyonu).
Bu durum genellikle bir sayfa veya bileşen ağacına özgüdür. Navigasyonla sıfırlanması sorun değildir.
Sunucu durumu API'dan gelen verilerdir: kullanıcı profilleri, ürün listeleri, izinler, bildirimler, kaydedilmiş ayarlar. Bu “uzaktan doğruluk”tır ve UI hiçbir şey yapmasa bile değişebilir (başkası düzenler, sunucu yeniden hesaplar, arka plan işi günceller).
Uzaktan olduğu için metadata gerekir: yükleme/hata durumları, önbellek zaman damgaları, yeniden denemeler ve geçersiz kılma (invalidation).
UI taslaklarını sunucu verisinin içine koyarsanız, bir refetch yerel düzenlemeleri silebilir. Sunucu yanıtlarını UI durumuna önbellekleme kuralları olmadan koyarsanız eski veri, tekrar eden fetch'ler ve tutarsız ekranlarla uğraşırsınız.
Yaygın bir başarısızlık modu: kullanıcı bir formu düzenlerken arka planda refetch gelir ve gelen cevap taslağın üzerine yazar.
Sunucu durumunu önbellekleme kalıplarıyla yönetin (fetch, cache, invalidate, focus ile refetch) ve bunu paylaşılan ve asenkron olarak ele alın.
UI durumunu yerel araçlarla yönetin (yerel bileşen durumu, gerçekten paylaşılan UI endişeleri için context) ve taslakları bilinçli olarak sunucuya “kaydet” deyinceye kadar ayrı tutun.
Türetilmiş durum, diğer durumlardan hesaplayabileceğiniz herhangi bir değerdir: satır öğelerinden sepet toplamı, orijinal liste + arama sorgusundan filtrelenmiş liste veya alan değerleri ve doğrulama kurallarından canSubmit bayrağı.
Bu değerleri saklamak cazip gelir çünkü pratik görünür (“ben de total'ı state'te tutarım”). Ama girdiler birden fazla yerde değiştiğinde sapma riski ortaya çıkar: saklanan total artık öğelerle eşleşmez, filtrelenmiş liste mevcut sorguyu yansıtmaz veya gönder butonu hatayı düzelttikten sonra hâlâ devre dışı kalır. Bu hatalar sinir bozucudur çünkü tek başına hiçbir şey “yanlış” görünmez—her değişken kendi başına geçerli ama birbirleriyle uyumsuzdur.
Daha güvenli bir desen: minimal kaynakları saklayın ve geri okuma sırasında her şeyi hesaplayın. React'te bu basit bir fonksiyon olabilir veya memoize edilmiş bir hesaplama.
const items = useCartItems();
const total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
const filtered = products.filter(p => p.name.includes(query));
Daha büyük uygulamalarda “seçiciler” (selectors) bu fikri resmileştirir: total, filteredProducts, visibleTodos nasıl türetileceğini tek bir yerde tanımlayan ve tüm bileşenlerin aynı mantığı kullanmasını sağlayan bir yapı.
Her render'da hesaplamak genelde iyidir. Gerçek bir maliyet ölçtüğünüzde önbelleğe alın: pahalı dönüşümler, devasa listeler veya birçok bileşen tarafından paylaşılan türetilmiş değerler. Memoizasyon (useMemo, selector memoizasyonu) kullanın; aksi halde gerçek girdiler dışındaki anahtarlarla cache yaparsanız yeniden sapma riski doğar.
Durum, “sahibi kim” belirsiz olduğunda can sıkıcı olur.
Bir durum parçasının sahibi, onu güncelleme hakkına sahip olan uygulama yeridir. Diğer UI parçaları onu okuyabilir (props, context, selector aracılığıyla), ama doğrudan değiştirmemelidir.
Açık sahiplik şu iki soruyu yanıtlar:
Bu sınırlar bulanıklaşırsa çakışan güncellemeler, “bunu neden değiştirdi?” anları ve yeniden kullanılabilirliği zor bileşenler elde edersiniz.
Durumu global bir store'a (veya üst seviye context'e) koymak temiz gelebilir: herkes ona erişir ve prop zincirini azaltırsınız. Ancak bedel istemeden bağlılıktir—ilişkisiz ekranlar aynı değerlere bağlanır ve küçük değişiklikler uygulama boyunca dalga etkisi yapar.
Küresel durum, gerçekten çapraz kesen (cross-cutting) şeyler için uygundur: geçerli kullanıcı oturumu, uygulama düzeyi özellik bayrakları veya paylaşılan bildirim kuyruğu gibi.
Yaygın bir desen yerelde başlamak ve iki kardeş parça koordinasyona ihtiyaç duyduğunda en yakın ortak üst elemana “lift” etmektir.
Eğer sadece bir bileşen ihtiyaç duyuyorsa, orada tutun. Birden fazla bileşen gerekiyorsa en küçük ortak sahipliğe taşıyın. Birçok uzak alan gerekiyorsa o zaman küresel düşünün.
Durumu kullanıldığı yere yakın tutun; paylaşım gerekmedikçe küreselleştirmeyin.
Bu, bileşenleri anlamayı kolaylaştırır, istemeden bağımlılıkları azaltır ve gelecekteki refaktörleri daha az korkutucu kılar çünkü daha az parça aynı veriyi değiştirme yetkisine sahiptir.
Frontend uygulamalar tek iş parçacıklı (single-threaded) hissedilir, ama kullanıcı girdisi, zamanlayıcılar, animasyonlar ve ağ istekleri bağımsız çalışır. Bu, birden fazla güncellemenin aynı anda yolda olabileceği ve başladıkları sırayla bitmeyebileceği anlamına gelir.
Yaygın bir çarpışma: UI'nın iki parçası aynı durumu güncelliyor.
query'yi güncelliyor.query'yi güncelliyor.Her biri tek başına doğruyken birlikte zamanlamaya bağlı olarak birbirlerinin üzerine yazabilir. Daha da kötüsü, yeni filtreleri gösterirken eski bir sorgunun sonuçlarını gösterebilirsiniz.
Yarış koşulları, istek A'yı gönderdikten sonra hızla istek B'yi göndermeniz ama istek A'nın son gelmesi durumunda ortaya çıkar.
Örnek: kullanıcı “c”, “ca”, “cat” yazıyor. Eğer “c” isteği yavaş, “cat” isteği hızlıysa UI önce “cat” sonuçlarını gösterip sonra eski “c” sonuçlarının gelmesiyle üzerine yazılabilir.
Hata ince çünkü her şey “çalıştı”—sadece yanlış sırada oldu.
Genellikle şu stratejilerden birini istersiniz:
AbortController ile).Basit bir istek ID yaklaşımı:
let latestRequestId = 0;
async function fetchResults(query) {
const requestId = ++latestRequestId;
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await res.json();
if (requestId !== latestRequestId) return; // stale response
setResults(data);
}
Optimizm UI'ı anlık hissettirir: sunucu onayından önce ekranı güncellersiniz. Ancak eşzamanlılık varsayımlarını bozabilir:
Optimizmi güvenli tutmak için genellikle açık bir uzlaşma kuralı gerekir: bekleyen eylemi takip edin, sunucu yanıtlarını sırayla uygulayın ve rollback gerekirse bilinen bir kontrol noktasına (checkpoint) geri dönün—not “UI şu an ne gösteriyorsa ona”.
Durum güncellemeleri “bedava” değildir. Durum değiştiğinde uygulama hangi ekran parçalarının etkilendiğini bulup yeni gerçeği yansıtmak için işe koyulur: değerleri yeniden hesaplar, UI'ı yeniden render eder, formatlama mantığını çalıştırır ve bazen veri tekrar alır veya doğrular. Bu zincir reaksiyonu gereğinden fazla büyükse kullanıcı bunu gecikme, takılma veya butonların tepkiyi “düşünmesi” olarak hisseder.
Tek bir anahtar bile gereksiz yere çok fazla işi tetikleyebilir:
Sonuç sadece teknik değildir—deneyimsel olarak hissedilir: yazma gecikir, animasyonlar takılır ve arayüzin “hızlı” olduğu hissi kaybolur.
En yaygın nedenlerden biri durumu aşırı geniş tutmaktır: birçok ilgisiz bilgiyi içeren büyük bir “kova” obje. Her alan güncellendiğinde tüm kova yenilenmiş gibi görünür ve daha fazla UI uyanır.
Bir diğer tuzak türetilmiş değerleri state'te tutmaktır. Bu genellikle ekstra güncellemelere (ve ekstra UI işine) yol açar sadece her şeyi uyumlu tutmak için.
Durumu daha küçük parçalara ayırın. İlgisiz endişeleri ayrı tutun ki arama girişi tüm sayfayı yenilemesin.
Verileri normalize edin. Aynı öğeyi birçok yerde tutmak yerine bir kere saklayıp referans verin. Bu tekrar eden güncellemeleri azaltır ve bir düzenlemenin birçok kopyayı yeniden yazmasına engel olur.
Türetilmiş değerleri memoize edin. Bir değer diğer durumdan hesaplanabiliyorsa (örn. filtrelenmiş sonuçlar), sadece girdiler değiştiğinde yeniden hesaplanacak şekilde cacheleyin.
İyi performans odaklı durum yönetimi büyük ölçüde kapsayıcılıkla ilgilidir: güncellemeler mümkün olan en küçük alanı etkilemeli ve pahalı işler gerçekten gerektiğinde çalışmalıdır. Bunu sağladığınızda kullanıcılar çerçeveyi fark etmeyi bırakır ve arayüze güvenmeye başlar.
Durum hataları çoğunlukla kişisel hissedilir: UI “yanlış”, ama en basit soruyu yanıtlayamazsınız—bu değeri kim ve ne zaman değiştirdi? Bir sayı ters döndüğünde, bir banner kaybolduğunda veya bir buton kilitlendiğinde bir zaman çizelgesine ihtiyacınız var, tahmine değil.
En hızlı açıklık yolu öngörülebilir bir güncelleme akışıdır. Reducer, event veya store kullanın; şu deseni hedefleyin:
setShippingMethod('express'), updateStuff yerine)Açık eylem kaydı (action logging), hata ayıklamayı “ekrana bakma”dan “fişi takip etmeye” çevirir. Basit console log'lar bile (eylem adı + ana alanlar) ne olduğunu yeniden kurmaya çalışmaktan daha iyidir.
Her yeniden render'ı test etmeye çalışmayın. Bunun yerine saf mantık gibi davranması gereken parçaları test edin:
Bu karışım hem “hesaplama hatalarını” hem de gerçek dünyadaki bağlantı sorunlarını yakalar.
Asenkron problemler boşluklarda gizlenir. Zaman çizelgelerini görünür kılacak minimal metadata ekleyin:
Böylece geç gelen bir cevap daha yeni olanın üzerine yazdığında bunu hemen ispatlayabilirsiniz—ve güvenle düzeltebilirsiniz.
Bir durum aracını seçmek, kararların bir sonucu olarak daha kolaydır—araçları karşılaştırmakla başlamak yerine sınırları haritalayın: hangi veriler bileşene yerel, hangileri paylaşılıyor ve hangileri gerçekten “sunucu verisi”.
Pratik bir karar için birkaç kısıtı göz önüne alın:
“Her yerde X kullanıyoruz” diye başlarsanız yanlış şeyleri yanlış yere saklarsınız. Önce sahiplik ile başlayın: bu değeri kim güncelliyor, kim okuyor ve değiştiğinde ne olmalı?
Birçok uygulama API verileri için bir server-state kütüphanesi ile yerel UI endişeleri için küçük bir çözüm kombinasyonuyla iyi gider. Amaç açıktır: her tür durum, akıl yürütülmesi en kolay yerde yaşasın.
Durum sınırları ve asenkron akışlar üzerinde iterasyon yapıyorsanız, Koder.ai deneme-izleme-düzeltme döngüsünü hızlandırabilir. Agent tabanlı iş akışıyla sohbet içinde React frontend (ve Go + PostgreSQL backend) üreterek farklı sahiplik modellerini (yerel vs küresel, sunucu önbelleği vs UI taslakları) hızlıca prototipleyip, öyle kalanı seçebilirsiniz.
Deney yaparken yardımcı iki pratik özellik: Planning Mode (durum modelini inşa etmeden önce taslak oluşturmak için) ve snapshots + rollback (türetilmiş durumu kaldırma veya istek ID'leri ekleme gibi refaktörleri kaybedip geri dönebileceğiniz güvenli testler için).
Durum tasarımını bir problem olarak ele aldığınızda işler kolaylaşır: kimin sahip olduğunu, neyi temsil ettiğini ve nasıl değiştiğini kararlaştırın. Bir bileşen gizemli hissetmeye başladığında bu kontrol listesini kullanın.
Sorun: Bu verinin sorumlusu uygulamanın hangi parçası? Durumu kullanıldığı yere mümkün olduğunca yakın koyun ve yalnızca gerçekten paylaşım gerekiyorsa yukarı taşıyın.
Bir şeyi diğer durumdan hesaplayabiliyorsanız saklamayın.
items, filterText).visibleItems).Asenkron işler daha net olduğunda daha kolay anlaşılır:
status: 'idle' | 'loading' | 'success' | 'error', artı data ve error.isLoading, isFetching, isSaving, hasLoaded, …) yerine tek bir status kullanın.Amaç: “bu duruma nasıl geldi?” hatalarını azaltmak, beş dosyaya dokunmadan değişiklik yapabilmek ve tek bir yere işaret edip burada gerçeklik yaşıyor diyebildiğiniz bir zihinsel model geliştirmek.