கிட்டத்தட்ட எந்தவொரு React codebase-ஐத் திறந்தாலும், அதே பழக்கத்தைக் காணலாம். ஒரு டெவலப்பர் ஒரு மதிப்பை (value) கண்காணிக்க வேண்டியிருக்கும் போது, அவர் useState-ஐப் பயன்படுத்துகிறார். ஒரு கவுண்டர் (counter) தேவையா? useState. ஒரு தற்காலிக இன்புட் மதிப்பு (input value)? useState. ஒரு மோடலை (modal) மாற்ற ஒரு பூலியன் (boolean)? useState. விரைவில், ஒரு சிங்கிள் காம்போனென்ட் (component) ஒரு டஜன் தனித்தனி ஹூக்குகளை (hooks) கொண்டிருக்கும், ஒவ்வொன்றும் தரவின் ஒரு சிறு பகுதியை நிர்வகிக்கும். இதன் விளைவாகக் குழப்பமான குறியீடு (noisy code), கூடுதல் ரீ-ரெண்டர்கள் (re-renders) மற்றும் சிதறிய நிலையில் இருக்கும் ஸ்டேட் (state) ஆகியவை உருவாகின்றன.

இந்த பழக்கம் புரிந்துகொள்ளத்தக்கது. நம்மில் பெரும்பாலோர் முதலில் கற்றுக்கொள்ளும் ஹூக் useState தான், மேலும் அது வேலை செய்கிறது. ஆனால் வேலை செய்வது என்பது சரியாகப் பொருந்துவது (fitting) என்று அர்த்தமல்ல. ஒவ்வொரு தரவையும் ஒரு reactive state ஆகக் கருதுவது, காம்போனென்ட் வளரும்போது மட்டுமே தெரியவரும் சிக்கல்களை உருவாக்குகிறது.

மாறுகிறது என்பதற்காகவே அதற்கு ஸ்டேட் (state) தேவையில்லை

காலப்போக்கில் மாறும் ஒவ்வொரு வேரியபிளும் (variable) useState-ல் இருக்க வேண்டிய அவசியமில்லை. சில மதிப்புகள் நீங்கள் ஏற்கனவே வைத்திருக்கும் வேறொரு விஷயத்தின் முடிவாக மட்டுமே இருக்கும். firstName மற்றும் lastName-ஐ இணைப்பதன் மூலம் ஒரு பயனரின் முழுப் பெயரை (full name) நீங்கள் ஸ்டேட்டில் சேமித்தால், இப்போது உங்களிடம் இரண்டு உண்மைகளின் ஆதாரங்கள் (two sources of truth) உள்ளன. பேரண்ட் (parent) ரீ-ரெண்டர் ஆவதால் firstName மாறும்போது, உங்கள் fullName ஸ்டேட் அதைச் சமன் செய்ய (sync) நீங்கள் மற்றொரு எஃபெக்ட்டை (effect) இயக்கும் வரை பழைய நிலையிலேயே இருக்கும். உங்களுக்கு ஒரு sync effect தேவையில்லை. உங்களுக்கு ஒரு derived value தேவை.

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

அதை ரெண்டரிங் (render) செய்யும் போதே கணக்கிடுங்கள். அந்த கணக்கீடு அதிகச் செலவு மிக்கது (expensive) என்றால், அதை memoize செய்யுங்கள். ஆனால் பயனர் அந்த முழுப் பெயரையும் மற்ற பாகங்களிலிருந்து தனித்து மாற்ற முடியாவிட்டால், அதற்குத் தனியாக ஒரு useState ஹூக்கை உருவாக்க வேண்டாம்.

இதே விதி ஃபில்டர் செய்யப்பட்ட பட்டியல்களுக்கும் (filtered lists) பொருந்தும். நீங்கள் allItems மற்றும் filteredItems ஆகிய இரண்டையும் ஸ்டேட்டில் வைத்திருந்தால், உங்கள் பராமரிப்புப் பணி (maintenance surface) இருமடங்காகிவிடும். ரெண்டரிங் செய்யும் போதே ஃபில்டர் செய்யுங்கள். மூல அரேவை (source array) மற்றும் ஃபில்டர் உரையை (filter text) ஸ்டேட்டில் வைத்துக்கொண்டு, அதன் மூலம் தெரியும் பட்டியலை (visible list) derived value ஆகப் பெறுங்கள். இது ஃபில்டர் செய்யப்பட்ட பட்டியல் மூலப் பட்டியலுடன் எப்போதும் சரியாக இருப்பதை உறுதி செய்கிறது.

சில மதிப்புகள் ரீ-ரெண்டர்களைத் (re-renders) தூண்டக்கூடாது

ஏதோ ஒன்று மாறியுள்ளது என்றும், DOM புதுப்பிக்கப்பட வேண்டியிருக்கலாம் என்றும் React-இடம் சொல்வதற்காகவே useState உருவாக்கப்பட்டது. ஒரு மதிப்பு மாறுகிறது ஆனால் UI-ன் எந்தப் பகுதியும் அந்த மாற்றத்தைக் கவலைப்படவில்லை என்றால், useRef சிறந்த கருவியாகும்.

டைமர்கள் (Timers) மற்றும் இன்டர்வெல்கள் (intervals) இதற்குச் சிறந்த உதாரணம். setInterval ஐடிகளை (IDs) ஸ்டேட்டில் சேமிப்பது, நீங்கள் ஒரு டைமரைத் தொடங்கும்போதோ அல்லது நிறுத்தும்போதோ ஒவ்வொரு முறையும் ரீ-ரெண்டரைத் தூண்டும், ஆனால் பயனர் அந்த இன்டர்வெல் ஐடியைப் பார்க்க முடியாது. ஒரு ref அந்த மதிப்பை React-க்குத் தெரியப்படுத்தாமல் வைத்திருக்கும். முந்தைய props-களைக் கண்காணிப்பது, பெயிண்ட் (paint) செய்வதற்கு முன் DOM நோட்களை (nodes) அளவிடுவது அல்லது ஒரு custom hook-க்கான சமீபத்திய callback-ஐச் சேமிப்பது போன்றவற்றுக்கும் இதே தர்க்கம் பொருந்தும். உங்களிடமே கேட்டுக்கொள்ளுங்கள்: இந்த மதிப்பு திரையில் தோன்ற வேண்டுமா? பதில் 'இல்லை' என்றால், அதற்கு useState தேவையில்லை.

DOM நோட்களும் refs-ல் தான் இருக்க வேண்டும். நீங்கள் ஒரு DOM எலிமெண்ட்டை (element) ஸ்டேட்டில் சேமிக்கலாம், ஆனால் அவ்வாறு செய்வது ref callback இயங்கிய பிறகு ரீ-ரெண்டரைத் தூண்டும். பெரும்பாலான சந்தர்ப்பங்களில், உங்களுக்கு அந்த நோட் ஒரு imperative method அல்லது அளவீட்டிற்கு (measurement) மட்டுமே தேவைப்படும், அதை வேறு விதமாக ரெண்டர் செய்யத் தேவைப்படாது.

பூலியன் வலை (The Boolean Trap)

ஒவ்வொரு ஃபிளாக் (flag)-க்கும் தனித்தனி ஹூக் இருக்கும்போது, தொடர்புடைய UI விஷயங்கள் கட்டுப்பாடின்றிப் பரவிவிடும். isLoading, isError, மற்றும் isSuccess ஆகிய மூன்று தனித்தனி பூலியன்களாக வரையறுக்கப்பட்ட காம்போனென்ட்களை நீங்கள் பார்ப்பீர்கள். பிரச்சனை என்னவென்றால், இந்த மூன்று நிலைகளும் (states) ஒன்றையொன்று சார்ந்து இல்லை. isLoading மற்றும் isSuccess ஆகிய இரண்டும் 'true' ஆக இருந்தால், உங்கள் UI சாத்தியமற்ற நிலையில் இருக்கும், இருப்பினும் TypeScript மற்றும் React அதை அப்படியே ரெண்டர் செய்ய அனுமதிக்கும்.

தொடர்புடைய ஸ்டேட்டைத் தொகுப்பதன் மூலம் (Grouping) இத்தகைய தவறான சேர்க்கைகளைத் தவிர்க்கலாம். மூன்று பூலியன்களுக்குப் பதிலாக, ஒரு ஒற்றை status string-ஐக் கண்காணிக்கவும்: 'idle', 'loading', 'success', அல்லது 'error'. ஒரே நேரத்தில் ஒன்று மட்டுமேச் செயல்பட முடியும், இது வகை அளவில் (type level) சாத்தியமற்ற நிலைகளை நீக்குகிறது. தரவு மிகவும் சிக்கலானதாக இருந்தால், ஒரு discriminated union கொண்ட ஆப்ஜெக்ட் (object) விஷயங்களை இன்னும் எளிமையாக்கும். ஒரே event handler-க்குள் பல useState அழைப்புகளை நீங்கள் அப்டேட் செய்கிறீர்கள் என்றால், அந்த மதிப்புகள் ஒன்றாக இருக்க வேண்டும் என்பதற்கான அறிகுறி அது.

மற்றொரு useState-க்கு பதிலாக useReducer-ஐப் பயன்படுத்துங்கள்

ஸ்டேட் அப்டேட்கள் (state updates) ஒரு குழப்பமான விளையாட்டாக மாறும் ஒரு கட்டம் உண்டு. நீங்கள் ஒரே பங்க்ஷனுக்குள் (function) setA, பிறகு setB, பிறகு நிபந்தனைக்குட்பட்டு setC என அழைக்கிறீர்கள். அந்த குறியீட்டைப் படிக்கும் அடுத்த டெவலப்பர், காம்போனென்ட் உண்மையில் என்ன செய்கிறது என்பதைப் புரிந்துகொள்ள அந்த வரிசையைத் தொடர வேண்டியிருக்கும்.

இங்கே useReducer சிறப்பாகச் செயல்படும். இது useState-ஐ விட மேம்பட்டது என்பதால் அதை மாற்றவில்லை; மாறாக தர்க்கம் (logic) தேவைப்படுவதால் அதை மாற்றுகிறது. ஒரு reducer ஸ்டேட் எவ்வாறு மாறுகிறது என்பதை மையப்படுத்துகிறது. event handlers முழுவதும் பல கட்டளைகளை (imperatives) சிதறவிடுவதைத் தவிர்த்து, நீங்கள் ஒரு நோக்கத்தை (intention) அனுப்புகிறீர்கள்: dispatch({ type: 'submitted' }). அடுத்த ஸ்டேட் எப்படி இருக்க வேண்டும் என்பதை reducer தீர்மானிக்கிறது. இது டெஸ்டிங்கை (testing) எளிதாக்குகிறது, ஏனெனில் உங்கள் ஸ்டேட் லாஜிக் ஒரு pure function ஆகும். இது டீபக்கிங்கையும் (debugging) எளிதாக்குகிறது, ஏனெனில் ஒவ்வொரு மாற்றமும் ஒரு டிராஸ் செய்யக்கூடிய (traceable) action-ஐ விட்டுச் செல்கிறது.

ஒரு reducer-ஐப் பயன்படுத்த Redux தேவையில்லை. மூன்று அல்லது அதற்கு மேற்பட்ட state மாறிகள் ஒன்றாகப் புதுப்பிக்கப்பட வேண்டும் என்றாலோ, அல்லது உங்கள் அடுத்த state முந்தையதைச் சார்ந்து பெரிதும் இருந்தாலோ, ஒரு reducer அந்த component-ஐ வியத்தகு முறையில் எளிதாக்கும்.

State உண்மையில் எங்கு உள்ளது

சில நேரங்களில் பிரச்சனை state-ஐ எப்படிச் சேமிப்பது என்பதில் இல்லை, எங்குச் சேமிப்பது என்பதில் தான் உள்ளது. ஒரு பொதுவான தவறு என்னவென்றால், வேறு எங்காவது தேவைப்படலாம் என்பதற்காகவே state-ஐ ஒரு parent component-க்கு hoisting செய்வது. ஒரு குறிப்பிட்ட state-ஐ ஒரு leaf component மட்டுமே பயன்படுத்துகிறது என்றால், அதை அங்கேயே வைத்திருக்கவும். இதுதான் colocation, இது மாற்றங்களால் ஏற்படும் பாதிப்பு எல்லையை (blast radius) குறைக்கிறது. ஒரு child component திறக்கப்படுவதால் parent component-ஐ re-render செய்ய வைக்காதீர்கள்.