લગભગ કોઈપણ React કોડબેઝ ખોલો અને તમે એ જ રિફ્લેક્સ જોશો. ડેવલપરને કોઈ વેલ્યુ ટ્રેક કરવાની જરૂર હોય છે, તેથી તેઓ useState નો ઉપયોગ કરે છે. કાઉન્ટર જોઈએ છે? useState. ટેમ્પરરી ઇનપુટ વેલ્યુ? useState. મોડલ ફ્લિપ કરવા માટે બુલિયન? useState. ટૂંક સમયમાં, એક સિંગલ કમ્પોનન્ટમાં ડઝનબંધ અલગ-અલગ હૂક્સ હોય છે, જેમાંથી દરેક ડેટાના એક નાના ભાગને મેનેજ કરે છે જેને રેન્ડર્સ દરમિયાન કાયમી રાખવાની જરૂર હોઈ શકે અથવા ન પણ હોય. પરિણામ એ છે કે કોડ અસ્તવ્યસ્ત (noisy) બને છે, વધારાના રી-રેન્ડર્સ થાય છે, અને સ્ટેટ કમ્પોનન્ટમાં અહીં-તહીં વિખરાયેલું રહે છે.

આ આદત સમજી શકાય તેવી છે. useState એ પ્રથમ હૂક છે જે આપણામાંથી મોટાભાગના લોકો શીખે છે, અને તે કામ કરે છે. પરંતુ 'કામ કરવું' એ 'યોગ્ય રીતે ફિટ થવું' જેવું નથી. દરેક ડેટાને reactive state તરીકે લેવાથી એવી સમસ્યાઓ ઊભી થાય છે જે કમ્પોનન્ટ મોટો થતાં જ દેખાય છે.

માત્ર તે બદલાય છે તેનો અર્થ એ નથી કે તેને State ની જરૂર છે

સમય જતાં બદલાતા દરેક વેરિએબલ useState માં હોવો જરૂરી નથી. કેટલાક મૂલ્યો માત્ર તમે પહેલેથી જ ધરાવતા અન્ય કોઈ વસ્તુનું પરિણામ હોય છે. જો તમે ફક્ત firstName અને lastName ને જોડીને (concatenate) યુઝરનું પૂરું નામ સ્ટેટમાં સ્ટોર કરો છો, તો તમારી પાસે હવે સત્યના બે સ્ત્રોત (two sources of truth) છે. જ્યારે પેરેન્ટ રી-રેન્ડર થાય ત્યારે firstName અપડેટ થાય છે, ત્યારે તમારું fullName સ્ટેટ ત્યાં સુધી જૂનું (stale) રહેશે જ્યાં સુધી તમે તેને સિંક કરવા માટે બીજો effect ન ચલાવો. તમારે સિંક ઇફેક્ટની જરૂર નથી. તમારે ડેરાઇવ્ડ વેલ્યુ (derived value) ની જરૂર છે.

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

તેને render દરમિયાન જ ગણતરી કરો. જો તે ગણતરી ખર્ચાળ (expensive) હોય, તો તેને memoize કરો. પરંતુ જ્યાં સુધી યુઝર તે પૂરું નામ તેના ભાગોથી સ્વતંત્ર રીતે એડિટ ન કરી શકે ત્યાં સુધી તેને પોતાનું અલગ useState હૂક ન આપો.

આ જ નિયમ ફિલ્ટર કરેલી લિસ્ટ (filtered lists) ને પણ લાગુ પડે છે. જો તમે સ્ટેટમાં allItems અને filteredItems બંને રાખો છો, તો તમે તમારા મેન્ટેનન્સનું કામ બમણું કરી દીધું છે. render દરમિયાન ફિલ્ટર કરો. સોર્સ એરે (source array) અને ફિલ્ટર ટેક્સ્ટને સ્ટેટમાં રાખો, અને પછી દેખાતી લિસ્ટ મેળવો. આ ખાતરી કરે છે કે ફિલ્ટર કરેલી લિસ્ટ સોર્સ સાથે ક્યારેય અસિંક (out of sync) ન થાય.

કેટલાક મૂલ્યોએ ક્યારેય Re-renders ટ્રિગર કરવા જોઈએ નહીં

useState ખાસ કરીને React ને જણાવવા માટે અસ્તિત્વ ધરાવે છે કે કંઈક બદલાયું છે અને DOM ને અપડેટ કરવાની જરૂર પડી શકે છે. જો કોઈ મૂલ્ય બદલાય છે પરંતુ UI નો કોઈ ભાગ તે ફેરફારની પરવા કરતો નથી, તો useRef એ વધુ સારું સાધન છે.

ટાઈમર્સ અને ઇન્ટરવલ્સ (Timers and intervals) તેનું ક્લાસિક ઉદાહરણ છે. સ્ટેટમાં setInterval IDs સ્ટોર કરવાથી દર વખતે જ્યારે તમે ટાઈમર શરૂ અથવા બંધ કરો છો ત્યારે re-render થાય છે, ભલે યુઝર ઇન્ટરવલ ID જોઈ શકતો ન હોય. એક ref તે મૂલ્યને React ને જાણ કર્યા વગર જાળવી રાખે છે. આ જ તર્ક અગાઉના props ટ્રેક કરવા, પેઇન્ટ (paint) કરતા પહેલા DOM નોડ્સ માપવા અથવા કસ્ટમ હૂક માટે લેટેસ્ટ callback સ્ટોર કરવા માટે પણ લાગુ પડે છે. તમારી જાતને પૂછો: શું આ મૂલ્ય સ્ક્રીન પર દેખાવું જોઈએ? જો જવાબ 'ના' હોય, તો કદાચ તેને useState ની જરૂર નથી.

DOM નોડ્સ પોતે પણ refs માં હોવા જોઈએ. જોકે તમે સ્ટેટમાં DOM એલિમેન્ટ સ્ટોર કરી શકો છો, પરંતુ તેમ કરવાથી ref callback ચાલ્યા પછી re-render ટ્રિગર થાય છે. મોટાભાગના કિસ્સાઓમાં, તમારે નોડની જરૂર માત્ર ઇમ્પેરેટિવ મેથડ (imperative method) અથવા માપન (measurement) માટે હોય છે, તેને અલગ રીતે રેન્ડર કરવા માટે નહીં.

બુલિયન ટ્રેપ (The Boolean Trap)

જ્યારે દરેક ફ્લેગને પોતાનું અલગ હૂક મળે છે, ત્યારે સંબંધિત UI બાબતો વણસી જાય છે. તમે કમ્પોનન્ટ્સ જોશો જેમાં isLoading, isError, અને isSuccess ને ત્રણ અલગ-અલગ બુલિયન તરીકે વ્યાખ્યાયિત કરવામાં આવ્યા હોય. સમસ્યા એ છે કે આ ત્રણેય સ્ટેટ્સ સ્વતંત્ર નથી. જો isLoading અને isSuccess બંને true હોય, તો તમારું UI અશક્ય સ્થિતિમાં છે, છતાં TypeScript અને React તમને તેમ રેન્ડર કરવા દેશે.

સંબંધિત સ્ટેટનું ગ્રુપિંગ કરવાથી આ અમાન્ય સંયોજનોને અટકાવી શકાય છે. ત્રણ બુલિયનને બદલે, એક સિંગલ સ્ટેટસ સ્ટ્રિંગ ટ્રેક કરો: 'idle', 'loading', 'success', અથવા 'error'. એક સમયે માત્ર એક જ સક્રિય હોઈ શકે છે, જે ટાઇપ લેવલ પર અશક્ય સ્ટેટ્સને દૂર કરે છે. જો ડેટા વધુ જટિલ હોય, તો discriminated union ધરાવતું ઓબ્જેક્ટ વસ્તુઓને વધુ સ્પષ્ટ બનાવે છે. જ્યારે તમે જુઓ છો કે તમે એક જ ઇવેન્ટ હેન્ડલરની અંદર અનેક useState કોલ્સ અપડેટ કરી રહ્યા છો, ત્યારે તે એક સંકેત છે કે તે મૂલ્યો સાથે હોવા જોઈએ.

બીજો useState નહીં, પણ useReducer નો ઉપયોગ કરો

એક એવો તબક્કો આવે છે જ્યાં સ્ટેટ અપડેટ્સ 'whack-a-mole' (એક પછી એક સમસ્યાઓ આવવી) જેવી બની જાય છે. તમે એક જ ફંક્શનની અંદર setA, પછી setB, અને પછી શરતી રીતે setC કોલ કરો છો. તે કોડ વાંચનાર પછીના ડેવલપર માટે કમ્પોનન્ટ ખરેખર શું કરે છે તે સમજવા માટે આખી શ્રેણીને ટ્રેસ કરવી પડે છે.

અહીં useReducer શ્રેષ્ઠ સાબિત થાય છે. તે useState ને એટલા માટે રિપ્લેસ કરતું નથી કારણ કે તે વધુ એડવાન્સ્ડ છે; તે useState ને એટલા માટે રિપ્લેસ કરે છે કારણ કે લોજિક તેની માંગ કરે છે. એક reducer સ્ટેટ કેવી રીતે બદલાય છે તેનું કેન્દ્રીકરણ કરે છે. ઇવેન્ટ હેન્ડલર્સમાં અલગ-અલગ ઇમ્પેરેટિવ્સ છાંટવાને બદલે, તમે એક ઈરાદો (intention) ડિસ્પેચ કરો છો: dispatch({ type: 'submitted' }). રેડ્યુસર નક્કી કરે છે કે આગલું સ્ટેટ કેવું દેખાશે. આ ટેસ્ટિંગને સરળ બનાવે છે, કારણ કે તમારું સ્ટેટ લોજિક એક pure function છે. તે ડિબગિંગને પણ સરળ બનાવે છે, કારણ કે દરેક ફેરફાર એક ટ્રેસેબલ એક્શન (traceable action) છોડે છે.

રિડ્યુસરનો ઉપયોગ કરવા માટે તમારે Redux ની જરૂર નથી. જો તમારી પાસે ત્રણ અથવા વધુ સ્ટેટ વેરિયેબલ્સ હોય જે એકસાથે અપડેટ થાય છે, અથવા જો તમારું આગામી સ્ટેટ અગાઉના સ્ટેટ પર ખૂબ આધારિત હોય, તો રિડ્યુસર કમ્પોનન્ટને નોંધપાત્ર રીતે સરળ બનાવે છે.

સ્ટેટ ખરેખર ક્યાં રહે છે

ક્યારેક સમસ્યા એ નથી કે તમે સ્ટેટ કેવી રીતે સ્ટોર કરો છો, પરંતુ તે ક્યાં સ્ટોર કરો છો તે છે. એક સામાન્ય ભૂલ એ છે કે સ્ટેટને ફક્ત એટલા માટે પેરેન્ટમાં હોઇસ્ટ (hoist) કરી દેવામાં આવે છે કારણ કે તેની જરૂર અન્ય જગ્યાએ પડી શકે છે. જો માત્ર એક જ લીફ કમ્પોનન્ટ સ્ટેટના કોઈ ભાગનો ઉપયોગ કરતું હોય, તો તેને ત્યાં જ રાખો. આ કોલોકેશન (colocation) છે, અને તે ફેરફારોની અસર (blast radius) ઘટાડે છે. કોઈ ચાઇલ્ડ ખુલવાને કારણે પેરેન્ટને રી-રેન્ડર ન કરો.