ನೀವು ಯಾವುದೇ React ಕೋಡ್‌ಬೇಸ್ ಅನ್ನು ತೆರೆದರೂ, ಒಂದೇ ರೀತಿಯ ಅಭ್ಯಾಸವನ್ನು ಗಮನಿಸಬಹುದು. ಒಬ್ಬ ಡೆವಲಪರ್‌ಗೆ ಒಂದು ಮೌಲ್ಯವನ್ನು (value) ಟ್ರ್ಯಾಕ್ ಮಾಡಬೇಕಾದಾಗ, ಅವರು ತಕ್ಷಣವೇ useState ಅನ್ನು ಬಳಸುತ್ತಾರೆ. ಒಂದು ಕೌಂಟರ್ ಬೇಕೆ? useState. ತಾತ್ಕಾಲಿಕ ಇನ್‌ಪುಟ್ ವ್ಯಾಲ್ಯೂ ಬೇಕೆ? useState. ಮಾಡಲ್ (modal) ಅನ್ನು ಫ್ಲಿಪ್ ಮಾಡಲು ಬೂಲಿಯನ್ ಬೇಕೆ? useState. ಸ್ವಲ್ಪ ಸಮಯದ ನಂತರ, ಒಂದೇ ಕಾಂಪೊನೆಂಟ್‌ನಲ್ಲಿ ಡಜನ್ಗಟ್ಟಲೆ ಪ್ರತ್ಯೇಕ ಹೂಕ್‌ಗಳು (hooks) ಇರುತ್ತವೆ, ಪ್ರತಿಯೊಂದೂ ರೆಂಡರ್‌ಗಳ ನಡುವೆ ಉಳಿಯಬೇಕೆ ಅಥವಾ ಬೇಡವೇ ಎಂಬ ಅಸ್ಪಷ್ಟ ಡೇಟಾವನ್ನು ನಿರ್ವಹಿಸುತ್ತಿರುತ್ತದೆ. ಇದರ ಪರಿಣಾಮ ಅಸ್ತವ್ಯಸ್ತವಾದ ಕೋಡ್, ಹೆಚ್ಚುವರಿ re-renders ಮತ್ತು ಕಾಂಪೊನೆಂಟ್‌ನಾದ್ಯಂತ ಚಲ್ಲಾಪಿಲ್ಲಿಯಾಗಿರುವ state ಆಗಿರುತ್ತದೆ.

ಈ ಅಭ್ಯಾಸವು ಅರ್ಥವಾಗುವಂತದ್ದಾಗಿದೆ. useState ಎಂಬುದು ನಮ್ಮಲ್ಲಿ ಹೆಚ್ಚಿನವರು ಕಲಿಯುವ ಮೊದಲ ಹೂಕ್ ಆಗಿದೆ ಮತ್ತು ಅದು ಕೆಲಸ ಮಾಡುತ್ತದೆ. ಆದರೆ ಕೆಲಸ ಮಾಡುವುದು ಎಂದರೆ ಅದು ಸರಿಯಾಗಿ ಹೊಂದಿಕೆಯಾಗುತ್ತದೆ (fitting) ಎಂದರ್ಥವಲ್ಲ. ಪ್ರತಿಯೊಂದು ಡೇಟಾ ತುಣುಕನ್ನು reactive state ಎಂದು ಪರಿಗಣಿಸುವುದು, ಕಾಂಪೊನೆಂಟ್ ಬೆಳೆದಂತೆ ಕಾಣಿಸಿಕೊಳ್ಳುವ ಸಮಸ್ಯೆಗಳನ್ನು ಸೃಷ್ಟಿಸುತ್ತದೆ.

ಬದಲಾಗುತ್ತಿದೆ ಎಂದಾಕ್ಷಣ ಅದಕ್ಕೆ state ಬೇಕೇನಲ್ಲ

ಕಾಲಾನಂತರದಲ್ಲಿ ಬದಲಾಗುವ ಪ್ರತಿಯೊಂದು variable ಕೂಡ useState ನಲ್ಲಿ ಇರಬೇಕೆಂದಿಲ್ಲ. ಕೆಲವು ಮೌಲ್ಯಗಳು ನೀವು ಈಗಾಗಲೇ ಹೊಂದಿರುವ ಯಾವುದೋ ವಿಷಯದ ಫಲಿತಾಂಶವಾಗಿರುತ್ತವೆ ಅಷ್ಟೆ. ನೀವು ಕೇವಲ firstName ಮತ್ತು lastName ಅನ್ನು ಸೇರಿಸಿ (concatenate) ಬಳಕೆದಾರರ ಪೂರ್ಣ ಹೆಸರನ್ನು state ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಿದರೆ, ಈಗ ನಿಮ್ಮ ಬಳಿ ಎರಡು ಸತ್ಯದ ಮೂಲಗಳು (sources of truth) ಇರುತ್ತವೆ. ಪೇರೆಂಟ್ (parent) ಕಾಂಪೊನೆಂಟ್ ರೆಂಡರ್ ಆದಾಗ firstName ಅಪ್‌ಡೇಟ್ ಆದರೆ, ನಿಮ್ಮ fullName state ಅನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಮತ್ತೊಂದು effect ಅನ್ನು ರನ್ ಮಾಡುವವರೆಗೆ ಅದು ಹಳೆಯದಾಗಿರುತ್ತದೆ (stale). ನಿಮಗೆ ಸಿಂಕ್ ಎಫೆಕ್ಟ್ ಅಗತ್ಯವಿಲ್ಲ. ನಿಮಗೆ ಬೇಕಿರುವುದು ಒಂದು derived value.

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

ಇದನ್ನು render ಮಾಡುವಾಗವೇ ಲೆಕ್ಕಹಾಕಿ (compute). ಒಂದು ವೇಳೆ ಈ ಲೆಕ್ಕಾಚಾರವು ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳುವದಾಗಿದ್ದರೆ (expensive), ಅದನ್ನು memoize ಮಾಡಿ. ಆದರೆ ಬಳಕೆದಾರರು ಆ ಪೂರ್ಣ ಹೆಸರನ್ನು ಇತರ ಭಾಗಗಳಿಂದ ಸ್ವತಂತ್ರವಾಗಿ ಎಡಿಟ್ ಮಾಡಬಹುದಾಗಿದ್ದರೆ ಮಾತ್ರ ಅದಕ್ಕೆ ಪ್ರತ್ಯೇಕ useState ಹೂಕ್ ನೀಡಿ.

ಅದೇ ನಿಯಮವು filtered lists ಗೂ ಅನ್ವಯಿಸುತ್ತದೆ. ನೀವು allItems ಮತ್ತು filteredItems ಎರಡನ್ನೂ state ನಲ್ಲಿ ಇಟ್ಟುಕೊಂಡರೆ, ನಿಮ್ಮ ನಿರ್ವಹಣೆಯ ಹೊರೆ (maintenance surface) ಎರಡರಷ್ಟು ಹೆಚ್ಚಾಗುತ್ತದೆ. Render ಮಾಡುವಾಗ ಫಿಲ್ಟರ್ ಮಾಡಿ. ಮೂಲ array ಮತ್ತು ಫಿಲ್ಟರ್ ಪಠ್ಯವನ್ನು (filter text) state ನಲ್ಲಿ ಇರಿಸಿ, ನಂತರ ಕಾಣಿಸುವ ಪಟ್ಟಿಯನ್ನು (visible list) derived value ಆಗಿ ಪಡೆಯಿರಿ. ಇದು ಫಿಲ್ಟರ್ ಮಾಡಿದ ಪಟ್ಟಿಯು ಮೂಲದೊಂದಿಗೆ ಎಂದಿಗೂ ಸಿಂಕ್ ತಪ್ಪದಂತೆ ಖಚಿತಪಡಿಸುತ್ತದೆ.

ಕೆಲವು ಮೌಲ್ಯಗಳು ಎಂದಿಗೂ re-renders ಅನ್ನು ಪ್ರಚೋದಿಸಬಾರದು

React ಗೆ ಏನೋ ಬದಲಾಗಿದೆ ಮತ್ತು DOM ಅನ್ನು ಅಪ್‌ಡೇಟ್ ಮಾಡಬೇಕಾಗಬಹುದು ಎಂದು ತಿಳಿಸಲು useState ಅಸ್ತಿತ್ವದಲ್ಲಿದೆ. ಒಂದು ಮೌಲ್ಯವು ಬದಲಾಗುತ್ತದೆಯಾದರೂ, UI ನ ಯಾವುದೇ ಭಾಗಕ್ಕೆ ಆ ಬದಲಾವಣೆಯ ಬಗ್ಗೆ ಕಾಳಜಿಯಿಲ್ಲದಿದ್ದರೆ, useRef ಉತ್ತಮ ಸಾಧನವಾಗಿದೆ.

Timers ಮತ್ತು intervals ಇದಕ್ಕೆ ಉತ್ತಮ ಉದಾಹರಣೆ. setInterval ID ಗಳನ್ನು state ನಲ್ಲಿ ಸಂಗ್ರಹಿಸುವುದು ನೀವು ಪ್ರತಿ ಬಾರಿ ಟೈಮರ್ ಪ್ರಾರಂಭಿಸಿದಾಗ ಅಥವಾ ನಿಲ್ಲಿಸಿದಾಗ re-render ಅನ್ನು ಉಂಟುಮಾಡುತ್ತದೆ, ಆದರೂ ಬಳಕೆದಾರರು ಆ interval ID ಅನ್ನು ನೋಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಒಂದು ref ಆ ಮೌಲ್ಯವನ್ನು React ಗೆ ತಿಳಿಸದೆ ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳುತ್ತದೆ. ಹಿಂದಿನ props ಗಳನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡುವುದು, ಪೇಂಟ್ ಮಾಡುವ ಮೊದಲು DOM nodes ಗಳನ್ನು ಅಳೆಯುವುದು ಅಥವಾ ಕಸ್ಟಮ್ ಹೂಕ್‌ಗಾಗಿ ಇತ್ತೀಚಿನ callback ಅನ್ನು ಸಂಗ್ರಹಿಸುವುದು ಇವೆಲ್ಲದಕ್ಕೂ ಇದೇ ತರ್ಕ ಅನ್ವಯಿಸುತ್ತದೆ. ನಿಮ್ಮನ್ನು ನೀವೇ ಕೇಳಿಕೊಳ್ಳಿ: ಈ ಮೌಲ್ಯವು ಪರದೆಯ ಮೇಲೆ ಕಾಣಿಸಿಕೊಳ್ಳಬೇಕೇ? ಉತ್ತರ 'ಇಲ್ಲ' ಎಂದಿದ್ದರೆ, ಅದಕ್ಕೆ ಬಹುಶಃ useState ಅಗತ್ಯವಿಲ್ಲ.

DOM nodes ಗಳೂ ಸಹ refs ನಲ್ಲೇ ಇರಬೇಕು. ನೀವು DOM ಎಲಿಮೆಂಟ್ ಅನ್ನು state ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಬಹುದು, ಆದರೆ ಹಾಗೆ ಮಾಡುವುದು ref callback ರನ್ ಆದ ನಂತರ re-render ಅನ್ನು ಪ್ರಚೋದಿಸುತ್ತದೆ. ಹೆಚ್ಚಿನ ಸಂದರ್ಭಗಳಲ್ಲಿ, ನಿಮಗೆ ಆ ನೋಡ್ (node) ಕೇವಲ ಒಂದು imperative method ಅಥವಾ ಅಳತೆಗಾಗಿ ಬೇಕಾಗಿರುತ್ತದೆ ಹೊರತು, ಅದನ್ನು ವಿಭಿನ್ನವಾಗಿ ರೆಂಡರ್ ಮಾಡಲು ಅಲ್ಲ.

ಬೂಲಿಯನ್ (Boolean) ಬಲೆ

ಪ್ರತಿಯೊಂದು flag ಗೂ ತನ್ನದೇ ಆದ hook ಇದ್ದಾಗ, ಸಂಬಂಧಿತ UI ವಿಷಯಗಳು ಹರಡಿಕೊಳ್ಳುತ್ತವೆ. isLoading, isError, ಮತ್ತು isSuccess ಎಂಬ ಮೂರು ಪ್ರತ್ಯೇಕ ಬೂಲಿಯನ್‌ಗಳಾಗಿ ವ್ಯಾಖ್ಯಾನಿಸಲಾದ ಕಾಂಪೊನೆಂಟ್‌ಗಳನ್ನು ನೀವು ನೋಡಬಹುದು. ಸಮಸ್ಯೆ ಏನೆಂದರೆ ಈ ಮೂರು ಸ್ಥಿತಿಗಳು (states) ಸ್ವತಂತ್ರವಾಗಿಲ್ಲ. ಒಂದು ವೇಳೆ isLoading ಮತ್ತು isSuccess ಎರಡೂ true ಆಗಿದ್ದರೆ, ನಿಮ್ಮ UI ಅಸಾಧ್ಯವಾದ ಸ್ಥಿತಿಯಲ್ಲಿದೆ, ಆದರೂ TypeScript ಮತ್ತು React ಅದನ್ನು ರೆಂಡರ್ ಮಾಡಲು ಬಿಡುತ್ತವೆ.

ಸಂಬಂಧಿತ state ಅನ್ನು ಗುಂಪು ಮಾಡುವುದು ಇಂತಹ ಅಸಿಂಧು ಸಂಯೋಜನೆಗಳನ್ನು ತಡೆಯುತ್ತದೆ. ಮೂರು ಬೂಲಿಯನ್‌ಗಳ ಬದಲಿಗೆ, ಒಂದೇ status string ಅನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡಿ: 'idle', 'loading', 'success', ಅಥವಾ 'error'. ಏಕಕಾಲದಲ್ಲಿ ಕೇವಲ ಒಂದು ಮಾತ್ರ ಸಕ್ರಿಯವಾಗಿರಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ, ಇದು type level ನಲ್ಲಿ ಅಸಾಧ್ಯವಾದ ಸ್ಥಿತಿಗಳನ್ನು ನಿವಾರಿಸುತ್ತದೆ. ಡೇಟಾ ಹೆಚ್ಚು ಸಂಕೀರ್ಣವಾಗಿದ್ದರೆ, a discriminated union ಹೊಂದಿರುವ ಒಂದು object ವಿಷಯಗಳನ್ನು ಇನ್ನಷ್ಟು ಸುಗಮಗೊಳಿಸುತ್ತದೆ. ಒಂದೇ event handler ಒಳಗೆ ಹಲವಾರು useState ಕರೆಗಳನ್ನು ಅಪ್‌ಡೇಟ್ ಮಾಡುತ್ತಿರುವುದನ್ನು ನೀವು ಕಂಡಾಗ, ಆ ಮೌಲ್ಯಗಳು ಒಟ್ಟಾಗಿ ಇರಬೇಕೆಂಬ ಸಂಕೇತವಿದು.

ಮತ್ತೊಂದು useState ಬದಲಿಗೆ useReducer ಬಳಸಿ

State updates ಗಳು ಅಸ್ತವ್ಯಸ್ತವಾದ ಹೋರಾಟದಂತಾಗುವ (whack-a-mole) ಒಂದು ಹಂತವಿರುತ್ತದೆ. ನೀವು ಒಂದೇ ಫಂಕ್ಷನ್ ಒಳಗೆ setA, ನಂತರ setB, ನಂತರ ಸಾಂದರ್ಭಿಕವಾಗಿ setC ಎಂದು ಕರೆಯುತ್ತೀರಿ. ಆ ಕೋಡ್ ಅನ್ನು ಓದುವ ಮುಂದಿನ ಡೆವಲಪರ್, ಕಾಂಪೊನೆಂಟ್ ವಾಸ್ತವವಾಗಿ ಏನು ಮಾಡುತ್ತದೆ ಎಂಬುದನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳಲು ಆ ಸರಣಿಯನ್ನು ಪತ್ತೆಹಚ್ಚಬೇಕಾಗುತ್ತದೆ.

ಇಲ್ಲಿ useReducer ಅದ್ಭುತವಾಗಿ ಕೆಲಸ ಮಾಡುತ್ತದೆ. ಇದು useState ಗಿಂತ ಹೆಚ್ಚು ಸುಧಾರಿತವಾಗಿದೆ ಎಂಬ ಕಾರಣಕ್ಕಲ್ಲ; ಬದಲಿಗೆ ತರ್ಕವು (logic) ಅದನ್ನು ಬಯಸುತ್ತದೆ ಎಂಬ ಕಾರಣಕ್ಕೆ useState ಅನ್ನು ಬದಲಿಸುತ್ತದೆ. ಒಂದು reducer ಸ್ಥಿತಿ ಹೇಗೆ ಬದಲಾಗುತ್ತದೆ ಎಂಬುದನ್ನು ಕೇಂದ್ರೀಕರಿಸುತ್ತದೆ. Event handlers across ಇಡೀ ಕೋಡ್‌ನಲ್ಲಿ imperatives ಅನ್ನು ಚಲ್ಲಾಪಿಲ್ಲಿಯಾಗಿ ಹಾಕುವ ಬದಲು, ನೀವು ಒಂದು ಉದ್ದೇಶವನ್ನು (intention) dispatch ಮಾಡುತ್ತೀರಿ: dispatch({ type: 'submitted' }). ಮುಂದಿನ state ಹೇಗಿರಬೇಕು ಎಂಬುದನ್ನು reducer ನಿರ್ಧರಿಸುತ್ತದೆ. ಇದು testing ಅನ್ನು ಸುಲಭವಾಗಿಸುತ್ತದೆ, ಏಕೆಂದರೆ ನಿಮ್ಮ state logic ಒಂದು pure function ಆಗಿರುತ್ತದೆ. ಇದು debugging ಅನ್ನು ಸುಲಭಗೊಳಿಸುತ್ತದೆ, ಏಕೆಂದರೆ ಪ್ರತಿಯೊಂದು ಬದಲಾವಣೆಯು ಪತ್ತೆಹಚ್ಚಬಹುದಾದ action ಅನ್ನು ಬಿಟ್ಟುಹೋಗುತ್ತದೆ.

reducer ಬಳಸಲು ನಿಮಗೆ Redux ಅಗತ್ಯವಿಲ್ಲ. ಒಂದು ವೇಳೆ ಮೂರು ಅಥವಾ ಅದಕ್ಕಿಂತ ಹೆಚ್ಚು state variables ಒಟ್ಟಿಗೆ ಅಪ್‌ಡೇಟ್ ಆಗುತ್ತಿದ್ದರೆ, ಅಥವಾ ನಿಮ್ಮ ಮುಂದಿನ state ಹಿಂದಿನ దాని ಮೇಲೆ ಹೆಚ್ಚು ಅವಲಂಬಿತವಾಗಿದ್ದರೆ, reducer component ಅನ್ನು ಗಣನೀಯವಾಗಿ ಸರಳಗೊಳಿಸುತ್ತದೆ.

State ವಾಸ್ತವವಾಗಿ ಎಲ್ಲಿ ಇರುತ್ತದೆ

ಕೆಲವೊಮ್ಮೆ ಸಮಸ್ಯೆ state ಅನ್ನು ಹೇಗೆ ಸಂಗ್ರಹಿಸುವುದು ಎಂಬುದಲ್ಲ, ಬದಲಾಗಿ ಅದು ಎಲ್ಲಿ ಇರುತ್ತದೆ ಎಂಬುದರಲ್ಲಿದೆ. ಬೇರೆಡೆ ಬೇಕಾಗಬಹುದು ಎಂಬ ಕಾರಣಕ್ಕೆ state ಅನ್ನು parent ಗೆ ಏರಿಸುವುದು (hoisting) ಒಂದು ಸಾಮಾನ್ಯ ತಪ್ಪಾಗಿದೆ. ಒಂದು leaf component ಮಾತ್ರ ಒಂದು ನಿರ್ದಿಷ್ಟ state ಅನ್ನು ಬಳಸುತ್ತಿದ್ದರೆ, ಅದನ್ನು ಅಲ್ಲೇ ಇರಿಸಿ. ಇದನ್ನು colocation ಎನ್ನಲಾಗುತ್ತದೆ, ಮತ್ತು ಇದು ಬದಲಾವಣೆಗಳಿಂದ ಉಂಟಾಗುವ ಪರಿಣಾಮದ ವ್ಯಾಪ್ತಿಯನ್ನು (blast radius) ಕಡಿಮೆ ಮಾಡುತ್ತದೆ. ಮಗು (child) ಸಂಯೋಜನೆಯು ತೆರೆದ ಕಾರಣಕ್ಕಾಗಿ parent ಅನ್ನು re-render ಮಾಡಬೇಡಿ.