React स्टेट प्रबंधन को सरल बनाएं: सर्वर स्टेट को क्लाइंट स्टेट से अलग रखें, कुछ नियम अपनाएँ, और बढ़ती जटिलता के शुरुआती संकेत पहचानें।

स्टेट कोई भी डेटा है जो आपकी ऐप के चलने के दौरान बदल सकता है। इसमें आप जो देखते हैं (एक मॉडेल खुला है), जो आप एडिट कर रहे हैं (एक फॉर्म ड्राफ्ट), और जो डेटा आप लाते हैं (प्रोजेक्ट्स की लिस्ट) — सब शामिल है। समस्या ये है कि इन सबको एक ही शब्द "स्टेट" कहते हैं, जबकि उनका व्यवहार बहुत अलग होता है।
ज्यादातर गंदे ऐप्स एक ही तरह से टूटते हैं: बहुत से स्टेट प्रकार एक ही जगह पर मिल जाते हैं। एक कंपोनेंट में सर्वर डेटा, UI फ़्लैग, फॉर्म ड्राफ्ट, और व्युत्पन्न मान (derived values) सब एक साथ रख दिए जाते हैं, और इन्हें effects से सिंक करने की कोशिश की जाती है। जल्द ही आप साधारण सवालों का जवाब नहीं दे पाते जैसे "यह वैल्यू कहां से आ रही है?" या "इसे कौन अपडेट करता है?" बिना कई फाइलों में खोज किए।
जनरेट की गई React ऐप्स में ये तेज़ी से होता है क्योंकि प्रथम काम करने वाली कॉपी को अपनाना आसान होता है। आप एक नया स्क्रीन जोड़ते हैं, किसी पैटर्न को कॉपी करते हैं, बग को एक और useEffect से पैच करते हैं, और अब आपके पास सत्य के दो स्रोत हो जाते हैं। अगर जनरेटर या टीम बीच में दिशा बदलती है (यहाँ लोकल स्टेट, वहां ग्लोबल स्टोर), तो कोडबेस पैटर्न इकट्ठा कर लेता है बजाय एक पर निर्माण करने के।
लक्ष्य साधारण है: कम तरह के स्टेट और खोजने के लिए कम जगहें। जब सर्वर डेटा का एक स्पष्ट घर और सिर्फ UI के लिए एक स्पष्ट घर हो, तो बग छोटे होते हैं और बदलाव जोखिम भरा महसूस नहीं करते।
"इसे उबाऊ रखें" का मतलब है कुछ नियमों का पालन करना:
एक ठोस उदाहरण: अगर यूज़र लिस्ट बैकएंड से आती है, तो उसे सर्वर स्टेट की तरह मानें और जहाँ उपयोग हो वहां से फ़ेच करें। अगर selectedUserId केवल डिटेल पैनल को ड्राइव करने के लिए है, तो उसे उसी पैनल के पास छोटा UI स्टेट रखें। इन्हें मिलाना ही जटिलता की शुरुआत है।
बूझने वाली अधिकतर React स्टेट समस्याएँ एक ही गड़बड़ी से शुरू होती हैं: सर्वर डेटा को UI स्टेट जैसा समझ लेना। उन्हें जल्दी अलग कर दें और स्टेट मैनेजमेंट शांत रहता है, भले ही आपकी ऐप बड़ी हो जाए।
सर्वर स्टेट बैकएंड की चीज़ें हैं: यूज़र्स, ऑर्डर, टास्क, परमिशन, प्राइस, फीचर फ्लैग। ये बिना आपकी ऐप के कुछ करने के भी बदल सकती हैं (दूसरे टैब ने अपडेट कर दिया, कोई एडमिन ने बदला, बैकग्राउंड जॉब चला दिया, डेटा की वैधता खत्म हुई)। क्योंकि ये साझा और बदलने वाली हैं, इन्हें फ़ेचिंग, कैशिंग, रिफेचिंग और एरर हैंडलिंग की ज़रूरत होती है।
क्लाइंट स्टेट वह है जो सिर्फ आपका UI अभी के लिए देखता/इस्तेमाल करता है: कौन-सा मॉड्यल खुला है, कौन-सा टैब सलेक्टेड है, फिल्टर टॉगल, सॉर्ट ऑर्डर, साइडबार का कलेप्स होना, एक ड्राफ्ट सर्च क्वेरी। अगर आप टैब बंद कर दें, तो इसे खोना ठीक है।
एक त्वरित टेस्ट: "क्या मैं पेज रिफ्रेश कर सकता हूँ और इसे सर्वर से फिर से बना सकता हूँ?"
एक और श्रेणी है व्युत्पन्न स्टेट (derived state), जो आपको अतिरिक्त स्टोर बनाने से बचाती है। यह वह मान है जिसे आप अन्य मानों से गणना करके पा सकते हैं, इसलिए आप इसे स्टोर नहीं करते। फ़िल्टर्ड लिस्ट, टोटल्स, isFormValid, और "खाली स्थिति दिखाएं" अक्सर यहाँ आते हैं।
उदाहरण: आप प्रोजेक्ट्स की एक सूची फ़ेच करते हैं (सर्वर स्टेट)। चुना गया फिल्टर और "नया प्रोजेक्ट" डायलॉग ओपन फ्लैग क्लाइंट स्टेट हैं। फ़िल्टर के बाद दिखाई दे रही सूची व्युत्पन्न स्टेट है। अगर आप दिखाई देने वाली सूची को अलग से स्टोर करते हैं, तो वह सिंक से हट जाएगी और आप "यह स्टेल क्यों है?" की बग्स का पीछा करेंगे।
यह विभाजन तब मदद करता है जब कोई टूल जैसे Koder.ai स्क्रीन जल्दी जनरेट करता है: बैकएंड डेटा को एक फ़ेचिंग लेयर में रखें, UI विकल्पों को घटकों के पास रखें, और कम्प्यूटेड वैल्यूज़ को स्टोर करने से बचें।
स्टेट तब दर्दनाक हो जाता है जब किसी डेटा का दो मालिक हो जाते हैं। सबसे तेज़ तरीका सादगी बनाए रखने का यह है कि तय करें किसका क्या मालिक है और उसी पर टिके रहें।
उदाहरण: आप यूज़र्स की लिस्ट फ़ेच करते हैं और एक सलेक्टेड यूज़र दिखाते हैं। आम गलती यह है कि पूरा selected user ऑब्जेक्ट स्टेट में रखा जाए। इसके बजाय selectedUserId स्टोर करें। लिस्ट सर्वर कैश में रहे। डिटेल व्यू रेंडर करते समय ID से यूज़र को देखें, ताकि रिफेचेस बिना एक्स्ट्रा सिंक कोड के UI अपडेट कर दें।
जनरेट की गई React ऐप्स में यह भी आम है कि "सहायक" जनरेटेड स्टेट सर्वर डेटा की नकल कर दे। जब आप fetch -> setState -> edit -> refetch जैसा कोड देखें, तो रुकें। अक्सर यह संकेत है कि आप ब्राउज़र में दूसरी डेटाबेस बना रहे हैं।
सर्वर स्टेट वह है जो बैकएंड पर रहता है: सूचियाँ, डिटेल पेज, सर्च रिजल्ट्स, परमिशन, काउंट्स। उबाऊ तरीका यह है कि एक टूल चुनें और उसी पर टिके रहें। कई React ऐप्स के लिए TanStack Query काफी होता है।
लक्ष्य सीधा है: कंपोनेंट्स डेटा माँगें, लोडिंग और एरर दिखाएँ, और यह न देखें कि अंदर कितनी फ़ेच कॉल हो रही हैं। यह जनरेट की गई ऐप्स में अहम है क्योंकि छोटी असंगतियाँ नई स्क्रीन जोड़ने पर तेजी से बढ़ती हैं।
क्वेरी कीज़ को एक नामकरण प्रणाली की तरह समझें, न कि बाद की सोच। इन्हें सुसंगत रखें: स्थिर array कीज़, केवल उन इनपुट्स को शामिल करें जो परिणाम बदलते हैं (फिल्टर्स, पेज, सॉर्ट), और कई एक-ऑफ कीज़ के बजाय कुछ पूर्वानुमेय शेप्स पसंद करें। कई टीमें कीज़ बनाने के छोटे हेल्पर्स रखती हैं ताकि हर स्क्रीन वही नियम उपयोग करे।
राइट ऑपरेशन्स के लिए mutations का उपयोग करें और सफलता पर स्पष्ट हैंडलिंग रखें। एक mutation को दो सवालों का जवाब देना चाहिए: क्या बदला, और UI को आगे क्या करना चाहिए?
उदाहरण: आप नया टास्क बनाते हैं। सफलता पर, या तो tasks लिस्ट क्वेरी को invalidate करें (ताकि वह एक बार फिर से लोड हो) या कैश को लक्षित तरीके से अपडेट करें (नया टास्क cached list में जोड़ दें)। फीचर के लिए एक तरीका चुनें और उसे लगातार रखें।
अगर आप कई जगहों पर "सुरक्षा के लिए" refetch कॉल जोड़ने का मन कर रहा है, तो एक ही उबाऊ कदम चुनें:
क्लाइंट स्टेट ब्राउज़र की चीज़ें हैं: साइडबार खुला है की फ्लैग, चुनी हुई रो, फिल्टर टेक्स्ट, सेव से पहले का ड्राफ्ट। इन्हें जहाँ उपयोग होता है पास में रखें और यह आमतौर पर मैनेजेबल रहता है।
छोटे से शुरू करें: निकटतम कंपोनेंट में useState। जब आप स्क्रीन जनरेट करते हैं (उदाहरण के लिए Koder.ai से), तो हर चीज़ को ग्लोबल स्टोर में डालने का लालच होता है "शायद आगे चाहिए"। इससे आपके पास एक ऐसा स्टोर बन जाता है जिसे कोई समझ नहीं पाता।
केवल तभी स्टेट ऊपर उठाएँ जब आप साझा करने वाली समस्या का नाम दे सकें।
उदाहरण: एक टेबल जिसमें डिटेल पैनल है, selectedRowId टेबल कंपोनेंट में रख सकते हैं। अगर पेज के किसी अन्य हिस्से का टूलबार भी इसे चाहिए, तो इसे पेज कंपोनेंट पर उठाएँ। अगर कोई अलग रूट (जैसे bulk edit) इसे चाहिए, तब ही छोटा स्टोर सार्थक होता है।
अगर आप स्टोर (Zustand या समान) उपयोग करते हैं, तो उसे एक ही काम पर केंद्रित रखें। स्टोर में "क्या" रखें (selected IDs, filters), न कि "परिणाम" (sorted lists) जिन्हें आप व्युत्पन्न कर सकते हैं।
जब स्टोर बढ़ने लगे, पूछें: क्या यह अभी भी एक फीचर है? अगर ईमानदार जवाब "थोड़ा सा" है, तो अभी अलग कर दें, इससे पहले कि अगला फीचर इसे छूने से डर लगे।
फॉर्म बग अक्सर इन तीन चीज़ों के मिल जाने से आते हैं: जो यूज़र टाइप कर रहा है, जो सर्वर में सेव है, और जो UI दिखा रहा है।
उबाऊ स्टेट मैनेजमेंट के लिए, फॉर्म को सबमिट करने तक क्लाइंट स्टेट समझें। सर्वर डेटा आख़िरी सेव्ड वर्शन है। फॉर्म एक ड्राफ्ट है। सर्वर ऑब्जेक्ट को स्थान पर एडिट न करें। वैल्यूज़ को ड्राफ्ट स्टेट में कॉपी करें, उपयोगकर्ता को स्वतंत्र रूप से बदलने दें, फिर सफलता पर सबमिट करें और रिफेच/कैश अपडेट करें।
यह तय करें कि यूज़र ने नेविगेट किया तो क्या परसेव होना चाहिए। एक बार यह निर्णय लेने से कई आश्चर्यजनक बग बचते हैं। उदाहरण के लिए, इनलाइन एडिट मोड और खुले ड्रॉपडाउन्स आमतौर पर रिसेट होने चाहिए, जबकि लंबा विज़ार्ड ड्राफ्ट या अनसेंट संदेश ड्राफ्ट शायद परसेव होना चाहिए। केवल तब परसेव करें जब उपयोगकर्ता स्पष्ट रूप से इसकी उम्मीद करता हो (जैसे चेकआउट फॉर्म)।
वैलिडेशन नियम एक ही जगह रखें। अगर आप नियमों को इनपुट्स, सबमिट हैंडलर, और हेल्पर्स में फैलाते हैं, तो आप मिसमैच्ड एरर्स पाएंगे। एक schema (या एक validate() फ़ंक्शन) पसंद करें, और UI तय करे कब एरर्स दिखाने हैं (on change, on blur, या on submit)।
उदाहरण: आप Koder.ai में एक Edit Profile स्क्रीन जनरेट करते हैं। सेव्ड प्रोफ़ाइल को सर्वर स्टेट के रूप में लोड करें। फॉर्म फ़ील्ड्स के लिए ड्राफ्ट स्टेट बनाएं। "अनसेव्ड चेंजेस" दिखाने के लिए ड्राफ्ट बनाम सेव्ड की तुलना करें। अगर यूज़र कैंसिल करता है, ड्राफ्ट रद्द कर दें और सर्वर वर्शन दिखाएँ। अगर सेव करता है, ड्राफ्ट सबमिट करें और सफलता पर सर्वर रिस्पॉन्स से सेव्ड वर्शन बदल दें।
जैसे-जैसे एक जनरेट की गई React ऐप बढ़ती है, सामान्यतः वही डेटा तीन जगह होते हुए मिल जाता है: कंपोनेंट स्टेट, एक ग्लोबल स्टोर, और एक कैश। समाधान आम तौर पर नया लाइब्रेरी नहीं होता। यह हर स्टेट आइटम के लिए एक घर चुनना है।
एक क्लीनअप फ्लो जो अधिकतर ऐप्स में काम करता है:
filteredUsers जैसा स्टेट हटाएँ अगर आप उसे users + filter से निकाल सकते हैं। selectedUser की बजाय selectedUserId पसंद करें।उदाहरण: एक Koder.ai जनरेटेड CRUD ऐप अक्सर useEffect फ़ेच प्लस ग्लोबल स्टोर कॉपी के साथ शुरू होता है। जब आप सर्वर स्टेट को केंद्रीकृत कर देते हैं, तो लिस्ट एक क्वेरी से आती है, और "रिफ्रेश" इनवैलिडेशन बन जाता है बजाय मैन्युअल सिंकिंग के।
नामकरण के लिए, सुसंगत और उबाऊ रखें:
users.list, users.detail(id)ui.isCreateModalOpen, filters.userSearchopenCreateModal(), setUserSearch(value)users.create, users.update, users.deleteलक्ष्य हर चीज के लिए एक स्रोत सत्य का होना और सर्वर व क्लाइंट स्टेट के बीच स्पष्ट सीमाएँ होना।
स्टेट की समस्याएँ छोटी होती हैं, फिर एक दिन आप फील्ड बदलते हैं और UI के तीन हिस्से असहमत हो जाते हैं कि "वास्तविक" मान क्या है।
सबसे स्पष्ट चेतावनी संकेत डुप्लिकेटेड डेटा है: वही यूज़र या कार्ट एक कंपोनेंट, एक ग्लोबल स्टोर, और एक रिक्वेस्ट कैश में रहता है। हर कॉपी अलग समय पर अपडेट होती है, और आप उन्हें बराबर रखने के लिए और कोड जोड़ते हैं।
एक और संकेत सिंक कोड है: effects जो स्टेट को आगे-पीछे धकेलते हैं। पैटर्न जैसे "जब क्वेरी डेटा बदलता है, स्टोर अपडेट करो" और "जब स्टोर बदलता है, रिफेच" तब तक काम कर सकते हैं जब तक कोई किनारा मामला stale वैल्यूज या लूप ट्रिगर न कर दे।
कुछ तेज़ रेड फ्लैग्स:
needsRefresh, didInit, isSaving जमा होते जा रहे हैं।उदाहरण: आप Koder.ai से एक डैशबोर्ड जनरेट करते हैं और एक Edit Profile मॉड्यल जोड़ते हैं। अगर प्रोफ़ाइल डेटा एक क्वेरी कैश में स्टोर है, ग्लोबल स्टोर में कॉपी है, और लोकल फॉर्म स्टेट में भी है, तो अब आपके पास सत्य के तीन स्रोत होंगे। जब आप बैकग्राउंड रिफेचिंग या ऑप्टिमिस्टिक अपडेट्स जोड़ते हैं, तो असंगतियाँ दिखने लगती हैं।
जब ये संकेत दिखें, उबाऊ चाल यह है कि हर डेटा के लिए एक मालिक चुनें और मिरर्स को हटा दें।
"शायद" के लिए चीज़ें स्टोर करना स्टेट को दर्दनाक बनाने का सबसे तेज़ तरीका है, खासकर जनरेट की गई ऐप्स में।
API रिस्पॉन्स को ग्लोबल स्टोर में कॉपी करना एक सामान्य जाल है। अगर डेटा सर्वर से आता है (लिस्ट, डिटेल, यूज़र प्रोफ़ाइल), तो इसे डिफ़ॉल्ट रूप से क्लाइंट स्टोर में कॉपी न करें। सर्वर डेटा का एक घर चुनें (आमतौर पर क्वेरी कैश)। क्लाइंट स्टोर का उपयोग सिर्फ उन UI-वैल्यूज़ के लिए करें जिनके बारे में सर्वर नहीं जानता।
व्युत्पन्न मानों को स्टोर करना एक और जाल है। काउंट्स, फ़िल्टर किए गए लिस्ट, टोटल्स, canSubmit, और isEmpty आमतौर पर इनपुट्स से गणना करके निकाले जाने चाहिए। अगर प्रदर्शन सच में समस्या बनता है, तो बाद में मेमोइज़ेशन करें, पर शुरुआत स्टोर करके न करें।
सब कुछ के लिए एक सिंगल मेगा-स्टोर (auth, मॉडलों, toasts, फिल्टर्स, ड्राफ्ट्स, onboarding फ़्लैग) एक डंपिंग ग्राउंड बन जाता है। फीचर के बॉउंडरी के हिसाब से बाँट दें। अगर स्टेट सिर्फ एक स्क्रीन इस्तेमाल कर रहा है, उसे लोकल रखें।
Context स्थिर मानों (theme, current user id, locale) के लिए बढ़िया है। तेज़ बदलते मानों के लिए यह व्यापक re-renders कर सकता है। वायरिंग के लिए Context उपयोग करें, और तेज़ बदलते UI मानों के लिए कंपोनेंट स्टेट (या छोटा स्टोर) उपयोग करें।
अंत में, अनुचित नामकरण से बचें। लगभग समान क्वेरी कीज़ और स्टोर फ़ील्ड्स नजाकत से डुप्लिकेशन पैदा करते हैं। एक सरल मानक चुनें और उसका पालन करें।
जब आपको "एक और" स्टेट वैरिएबल जोड़ने की इच्छा हो, तो एक त्वरित ओनरशिप चेक करें।
पहला, क्या आप एक जगह बता सकते हैं जहाँ सर्वर फ़ेचिंग और कैशिंग होती है (एक क्वेरी टूल, एक सेट क्वेरी कीज़)? अगर वही डेटा कई कंपोनेंट्स में फ़ेच होता है और स्टोर में भी कॉपी होता है, तो आप पहले से ही ब्याज चुका रहे हैं।
दूसरा, क्या यह वैल्यू सिर्फ एक स्क्रीन के अंदर चाहिए (जैसे "क्या फिल्टर पैनल खुला है")? अगर हाँ, तो यह ग्लोबल नहीं होना चाहिए।
तीसरा, क्या आप ऑब्जेक्ट की बजाय ID स्टोर कर सकते हैं? selectedUserId स्टोर करें और कैश या लिस्ट से यूज़र पढ़ें।
चौथा, क्या यह व्युत्पन्न है? अगर आप इसे मौजूदा स्टेट से निकाल सकते हैं, तो स्टोर न करें।
अंत में, एक मिनट का ट्रेस टेस्ट करें। अगर कोई टीममेट एक मिनट में नहीं बता सकता "यह वैल्यू कहाँ से आती है?" (prop, local state, server cache, URL, store), तो नए स्टेट से पहले ओनरशिप ठीक करें।
सोचिए एक जनरेटेड एडमिन ऐप (उदाहरण के लिए, Koder.ai से जनरेट किया हुआ) जिसमें तीन स्क्रीन हैं: कस्टमर लिस्ट, कस्टमर डिटेल पेज, और एक एडिट फॉर्म।
स्टेट शांत तब रहता है जब उसके स्पष्ट घर हों:
लिस्ट और डिटेल पेज क्वेरी कैश से सर्वर स्टेट पढ़ते हैं। जब आप सेव करते हैं, तो डेटा को ग्लोबल स्टोर में फिर से न स्टोर करें। आप म्यूटेशन भेजें, फिर कैश रिफ्रेश या अपडेट कर दें।
एडिट स्क्रीन के लिए, फॉर्म ड्राफ्ट लोकल रखें। इसे फ़ेच किए गए कस्टमर से इनिशियलाइज़ करें, पर यूज़र टाइपिंग शुरू करते ही इसे अलग मानें। इस तरह डिटेल व्यू सुरक्षित रूप से रिफ्रेश हो सकता है बिना आधे-बचे बदलावों को ओवरराइट किए।
ऑप्टिमिस्टिक UI वह जगह है जहाँ टीमें अक्सर सब कुछ डुप्लिकेट कर देती हैं। ज़रूरी नहीं कि आप ऐसा करें।
जब यूज़र Save दबाता है, केवल cached customer रिकॉर्ड और मेल खाने वाली लिस्ट आइटम अपडेट करें, और अगर रिक्वेस्ट फेल हो तो rollback करें। ड्राफ्ट फॉर्म तब तक रखें जब तक सेव सफल न हो। अगर फेल होता है, एरर दिखाएँ और ड्राफ्ट रखें ताकि यूज़र फिर से कोशिश कर सके।
मान लीजिए आप bulk edit जोड़ते हैं और उसे भी selected rows चाहिए। नया स्टोर बनाते से पहले पूछें: क्या यह स्टेट नेविगेशन और रिफ्रेश सहन करेगा?
जनरेट की गई स्क्रीन तेज़ी से बढ़ सकती हैं, और यह शानदार है जब तक कि हर नई स्क्रीन अपने स्टेट निर्णय न लाए।
रीपो में एक छोटी टीम नोट लिखें: क्या सर्वर स्टेट है, क्या क्लाइंट स्टेट है, और हर चीज़ का मालिक कौन सा टूल है। इसे इतना छोटा रखें कि लोग वास्तव में पालन करें।
एक छोटा पुल रिक्वेस्ट अभ्यास जोड़ें: हर नए स्टेट पीस को सर्वर या क्लाइंट के रूप में लेबल करें। अगर यह सर्वर स्टेट है, पूछें "यह कहाँ लोड होता है, कैसे कैश होता है, और क्या इसे इनवैलिडेट करता है?" अगर यह क्लाइंट स्टेट है, पूछें "कौन इसका मालिक है, और कब यह रिसेट होता है?"
अगर आप Koder.ai (koder.ai) उपयोग कर रहे हैं, तो Planning Mode मदद कर सकता है कि नई स्क्रीन जनरेट करने से पहले ओनरशिप पर सहमति बने। स्नैपशॉट और रोलबैक आपको एक सुरक्षित तरीका देते हैं जब स्टेट बदलाव गड़बड़ हो जाए तो वापस जाने का।
एक फीचर चुनें (जैसे edit profile), नियम पूरे लागू करें, और उसे उदाहरण बनें जिसे हर कोई कॉपी करे।
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.