लगभग किसी भी React codebase को खोलें और आपको एक ही तरह की आदत दिखेगी। एक डेवलपर को किसी वैल्यू को ट्रैक करने की ज़रूरत होती है, तो वे useState का उपयोग करते हैं। काउंटर चाहिए? useState। एक अस्थायी इनपुट वैल्यू? useState। मोडल को टॉगल करने के लिए एक boolean? useState। जल्द ही, एक ही component में दर्जनों अलग-अलग hooks हो जाते हैं, जिनमें से प्रत्येक डेटा के एक छोटे से हिस्से को मैनेज करता है जिसे शायद renders के दौरान बनाए रखने की आवश्यकता हो या न हो। इसका परिणाम शोर भरा (noisy) कोड, अतिरिक्त re-renders, और component में बिखरा हुआ state होता है।

यह आदत समझ में आती है। useState हम में से अधिकांश द्वारा सीखा जाने वाला पहला hook है, और यह काम करता है। लेकिन काम करने का मतलब यह नहीं है कि वह सही तरीके से फिट बैठता है। हर डेटा को reactive state मानने से ऐसी समस्याएँ पैदा होती हैं जो component के बड़े होने पर ही सामने आती हैं।

सिर्फ इसलिए कि यह बदलता है, इसका मतलब यह नहीं है कि इसे State की आवश्यकता है

समय के साथ बदलने वाला हर variable useState में नहीं होना चाहिए। कुछ वैल्यूज़ केवल आपके पास पहले से मौजूद किसी चीज़ का परिणाम होती हैं। यदि आप केवल firstName और lastName को जोड़ने (concatenate) के कारण उपयोगकर्ता का पूरा नाम state में स्टोर करते हैं, तो अब आपके पास truth के दो स्रोत (sources of truth) हैं। जब parent के re-render होने के कारण firstName अपडेट होता है, तो आपका fullName state तब तक पुराना (stale) रहता है जब तक कि आप इसे सिंक करने के लिए कोई दूसरा effect न चलाएं। आपको sync effect की आवश्यकता नहीं है। आपको एक derived value की आवश्यकता है।

const fullName = `${firstName} ${lastName}`;

इसे render के दौरान ही compute करें। यदि derivation महंगा (expensive) है, तो इसे memoize करें। लेकिन इसे अपना खुद का useState hook तब तक न दें जब तक कि उपयोगकर्ता उस पूरे नाम को उसके हिस्सों से स्वतंत्र रूप से एडिट न कर सके।

यही नियम filtered lists पर भी लागू होता है। यदि आप state में allItems और filteredItems दोनों रखते हैं, तो आपने अपने maintenance surface को दोगुना कर दिया है। Render के दौरान ही filter करें। source array और filter text को state में रखें, फिर visible list को derive करें। यह सुनिश्चित करता है कि filtered list कभी भी source के साथ out of sync न हो।

कुछ Values को कभी भी Re-renders ट्रिगर नहीं करने चाहिए

useState विशेष रूप से React को यह बताने के लिए मौजूद है कि कुछ बदला है और DOM को अपडेट करने की आवश्यकता हो सकती है। यदि कोई वैल्यू बदलती है लेकिन UI का कोई भी हिस्सा उस बदलाव की परवाह नहीं करता है, तो useRef एक बेहतर टूल है।

Timers और intervals इसका क्लासिक उदाहरण हैं। state में setInterval IDs को स्टोर करने से हर बार timer शुरू या बंद होने पर re-render होता है, भले ही उपयोगकर्ता interval ID को न देख सके। एक ref React को सूचित किए बिना उस वैल्यू को रखता है। यही तर्क पिछले props को ट्रैक करने, paint से पहले DOM nodes को मापने, या custom hook के लिए नवीनतम callback को स्टोर करने पर भी लागू होता है। खुद से पूछें: क्या इस वैल्यू को स्क्रीन पर दिखने की आवश्यकता है? यदि उत्तर 'नहीं' है, तो शायद इसे useState की आवश्यकता नहीं है।

DOM nodes स्वयं भी refs में होने चाहिए। हालाँकि आप state में एक DOM element स्टोर कर सकते हैं, ऐसा करने से ref callback चलने के बाद re-render ट्रिगर होता है। अधिकांश मामलों में, आपको नोड की आवश्यकता केवल एक imperative method या measurement के लिए होती है, उसे अलग तरह से render करने के लिए नहीं।

बूलियन का जाल (The Boolean Trap)

जब हर flag को अपना खुद का hook मिलता है, तो संबंधित UI संबंधी चिंताएँ फैलने लगती हैं। आप ऐसे components देखते हैं जिनमें isLoading, isError, और isSuccess को तीन अलग-अलग booleans के रूप में परिभाषित किया गया है। समस्या यह है कि ये तीन states स्वतंत्र नहीं हैं। यदि isLoading और isSuccess दोनों true हैं, तो आपका UI एक असंभव स्थिति में है, फिर भी TypeScript और React आपको इसे render करने देंगे।

संबंधित state को group करने से इन invalid combinations से बचा जा सकता है। तीन booleans के बजाय, एक single status string को track करें: 'idle', 'loading', 'success', या 'error'। एक समय में केवल एक ही active हो सकता है, जो type level पर असंभव states को समाप्त कर देता है। यदि डेटा अधिक जटिल है, तो एक discriminated union वाला object चीज़ों को और भी साफ कर देता है। जब आप खुद को एक ही event handler के अंदर कई useState calls को update करते हुए पाते हैं, तो यह एक संकेत है कि वे values एक साथ होनी चाहिए।

useReducer का उपयोग करें, न कि एक और useState

एक ऐसा बिंदु आता है जहाँ state updates 'whack-a-mole' बन जाते हैं। आप एक ही function के अंदर setA, फिर setB, और फिर सशर्त (conditionally) setC कॉल करते हैं। उस code को पढ़ने वाले अगले डेवलपर को यह समझने के लिए पूरी sequence को ट्रैक करना होगा कि component वास्तव में क्या करता है।

useReducer यहाँ चमकता है। यह useState को इसलिए नहीं बदलता क्योंकि यह अधिक उन्नत है; यह useState को इसलिए बदलता है क्योंकि logic इसकी मांग करता है। एक reducer यह centralize करता है कि state कैसे बदलती है। Event handlers में imperatives को बिखेरने के बजाय, आप एक intention dispatch करते हैं: dispatch({ type: 'submitted' })। Reducer तय करता है कि अगला state कैसा दिखेगा। इससे testing बहुत आसान हो जाती है, क्योंकि आपका state logic एक pure function है। यह debugging को भी आसान बनाता है, क्योंकि हर बदलाव एक traceable action छोड़ता है।

You do not need Redux to justify a reducer. If you have three or more state variables that update together, or if your next state depends heavily on the previous one, a reducer simplifies the component dramatically.

Where the State Actually Lives

Sometimes the issue is not how you store state, but where. A common mistake is hoisting state to a parent simply because it might be needed elsewhere. If only one leaf component uses a piece of state, keep it there. This is colocation, and it reduces the blast radius of changes. Do not make the parent re-render because a child opened