ਲਗਭਗ ਕੋਈ ਵੀ React codebase ਖੋਲ੍ਹੋ ਅਤੇ ਤੁਹਾਨੂੰ ਇੱਕੋ ਜਿਹੀ ਪ੍ਰਤੀਵਿਰਤੀ (reflex) ਦਿਖਾਈ ਦੇਵੇਗੀ। ਇੱਕ developer ਨੂੰ ਕਿਸੇ value ਨੂੰ track ਕਰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ, ਇਸ ਲਈ ਉਹ useState ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਇੱਕ counter ਚਾਹੀਦਾ ਹੈ? useState। ਇੱਕ temporary input value? useState। ਇੱਕ modal ਨੂੰ flip ਕਰਨ ਲਈ ਇੱਕ boolean? useState। ਜਲਦੀ ਹੀ, ਇੱਕ ਸਿੰਗਲ component ਵਿੱਚ ਦਰਜਨਾਂ ਵੱਖਰੇ hooks ਹੁੰਦੇ ਹਨ, ਜਿਨ੍ਹਾਂ ਵਿੱਚੋਂ ਹਰ ਇੱਕ ਡੇਟਾ ਦੇ ਇੱਕ ਛੋਟੇ ਹਿੱਸੇ ਨੂੰ manage ਕਰ ਰਿਹਾ ਹੁੰਦਾ ਹੈ ਜੋ ਸ਼ਾਇਦ re-renders ਦੌਰਾਨ ਬਣਿਆ ਰਹਿਣ ਦੀ ਲੋੜ ਹੋਵੇ ਜਾਂ ਨਾ। ਨਤੀਜਾ ਹੈ ਸ਼ੋਰ ਵਾਲਾ code, ਵਾਧੂ re-renders, ਅਤੇ component ਵਿੱਚ ਖਿੱਲਰੀ ਹੋਈ state।

ਇਹ ਆਦਤ ਸਮਝਣਯੋਗ ਹੈ। useState ਉਹ ਪਹਿਲਾ hook ਹੈ ਜੋ ਸਾਡੇ ਵਿੱਚੋਂ ਜ਼ਿਆਦਾਤਰ ਸਿੱਖਦੇ ਹਨ, ਅਤੇ ਇਹ ਕੰਮ ਕਰਦਾ ਹੈ। ਪਰ ਕੰਮ ਕਰਨਾ ਅਤੇ ਢੁਕਵਾਂ ਹੋਣਾ ਇੱਕੋ ਗੱਲ ਨਹੀਂ ਹੈ। ਹਰ ਡੇਟਾ ਦੇ ਟੁਕੜੇ ਨੂੰ reactive state ਵਜੋਂ ਮੰਨਣਾ ਅਜਿਹੀਆਂ ਸਮੱਸਿਆਵਾਂ ਪੈਦਾ ਕਰਦਾ ਹੈ ਜੋ ਉਦੋਂ ਹੀ ਸਾਹਮਣੇ ਆਉਂਦੀਆਂ ਹਨ ਜਦੋਂ component ਵੱਡਾ ਹੋ ਜਾਂਦਾ ਹੈ।

ਸਿਰਫ਼ ਇਸ ਲਈ ਕਿ ਇਹ ਬਦਲਦਾ ਹੈ, ਇਸਦਾ ਮਤਲਬ ਇਹ ਨਹੀਂ ਕਿ ਇਸਨੂੰ state ਦੀ ਲੋੜ ਹੈ

ਸਮੇਂ ਦੇ ਨਾਲ ਬਦਲਣ ਵਾਲਾ ਹਰ variable useState ਵਿੱਚ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ। ਕੁਝ values ਸਿਰਫ਼ ਕਿਸੇ ਹੋਰ ਚੀਜ਼ ਦਾ ਨਤੀਜਾ ਹੁੰਦੀਆਂ ਹਨ ਜੋ ਤੁਹਾਡੇ ਕੋਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ। ਜੇਕਰ ਤੁਸੀਂ ਸਿਰਫ਼ firstName ਅਤੇ lastName ਨੂੰ concatenate ਕਰਕੇ user ਦਾ ਪੂਰਾ ਨਾਮ state ਵਿੱਚ ਸਟੋਰ ਕਰਦੇ ਹੋ, ਤਾਂ ਹੁਣ ਤੁਹਾਡੇ ਕੋਲ ਦੋ sources of truth ਹਨ। ਜਦੋਂ parent re-render ਹੋਣ ਕਰਕੇ firstName ਅਪਡੇਟ ਹੁੰਦਾ ਹੈ, ਤਾਂ ਤੁਹਾਡੀ fullName state ਉਦੋਂ ਤੱਕ stale ਰਹਿੰਦੀ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਸਨੂੰ sync ਕਰਨ ਲਈ ਕੋਈ ਹੋਰ effect ਨਹੀਂ ਚਲਾਉਂਦੇ। ਤੁਹਾਨੂੰ sync effect ਦੀ ਲੋੜ ਨਹੀਂ ਹੈ। ਤੁਹਾਨੂੰ ਇੱਕ derived value ਦੀ ਲੋੜ ਹੈ।

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

ਇਸਨੂੰ render ਦੌਰਾਨ compute ਕਰੋ। ਜੇਕਰ derivation ਮਹਿੰਗਾ (expensive) ਹੈ, ਤਾਂ ਇਸਨੂੰ memoize ਕਰੋ। ਪਰ ਇਸਨੂੰ ਆਪਣਾ ਵੱਖਰਾ useState hook ਨਾ ਦਿਓ ਜਦੋਂ ਤੱਕ user ਉਸ ਪੂਰੇ ਨਾਮ ਨੂੰ ਹਿੱਸਿਆਂ ਤੋਂ ਸੁਤੰਤਰ ਤੌਰ 'ਤੇ edit ਨਹੀਂ ਕਰ ਸਕਦਾ।

ਇਹੀ ਨਿਯਮ 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 trigger ਨਹੀਂ ਕਰਨੇ ਚਾਹੀਦੇ

useState ਖਾਸ ਤੌਰ 'ਤੇ React ਨੂੰ ਇਹ ਦੱਸਣ ਲਈ ਮੌਜੂਦ ਹੈ ਕਿ ਕੁਝ ਬਦਲ ਗਿਆ ਹੈ ਅਤੇ DOM ਨੂੰ ਅਪਡੇਟ ਕਰਨ ਦੀ ਲੋੜ ਹੋ ਸਕਦੀ ਹੈ। ਜੇਕਰ ਕੋਈ value ਬਦਲਦੀ ਹੈ ਪਰ UI ਦਾ ਕੋਈ ਵੀ ਹਿੱਸਾ ਉਸ ਬਦਲਾਅ ਦੀ ਪਰਵਾਹ ਨਹੀਂ ਕਰਦਾ, ਤਾਂ useRef ਇੱਕ ਬਿਹਤਰ tool ਹੈ।

Timers ਅਤੇ intervals ਇੱਕ ਕਲਾਸਿਕ ਉਦਾਹਰਣ ਹਨ। State ਵਿੱਚ setInterval IDs ਨੂੰ ਸਟੋਰ ਕਰਨ ਨਾਲ ਹਰ ਵਾਰ timer ਸ਼ੁਰੂ ਜਾਂ ਬੰਦ ਕਰਨ 'ਤੇ re-render ਹੁੰਦਾ ਹੈ, ਭਾਵੇਂ user interval ID ਨੂੰ ਨਹੀਂ ਦੇਖ ਸਕਦਾ। ਇੱਕ ref ਉਸ value ਨੂੰ React ਨੂੰ ਨੋਟੀਫਾਈ ਕੀਤੇ ਬਿਨਾਂ ਰੱਖਦਾ ਹੈ। ਇਹੀ ਤਰਕ ਪਿਛਲੇ props ਨੂੰ track ਕਰਨ, paint ਤੋਂ ਪਹਿਲਾਂ DOM nodes ਨੂੰ ਮਾਪਣ, ਜਾਂ ਕਿਸੇ custom hook ਲਈ ਆਖਰੀ callback ਨੂੰ ਸਟੋਰ ਕਰਨ ਲਈ ਵੀ ਲਾਗੂ ਹੁੰਦਾ ਹੈ। ਆਪਣੇ ਆਪ ਨੂੰ ਪੁੱਛੋ: ਕੀ ਇਸ value ਨੂੰ ਸਕ੍ਰੀਨ 'ਤੇ ਦਿਖਾਈ ਦੇਣ ਦੀ ਲੋੜ ਹੈ? ਜੇਕਰ ਜਵਾਬ 'ਨਹੀਂ' ਹੈ, ਤਾਂ ਸ਼ਾਇਦ ਇਸਨੂੰ useState ਦੀ ਲੋੜ ਨਹੀਂ ਹੈ।

DOM nodes ਖੁਦ ਵੀ refs ਵਿੱਚ ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ। ਹਾਲਾਂਕਿ ਤੁਸੀਂ state ਵਿੱਚ ਇੱਕ DOM element ਨੂੰ ਸਟੋਰ ਕਰ ਸਕਦੇ ਹੋ, ਅਜਿਹਾ ਕਰਨ ਨਾਲ ref callback ਚੱਲਣ ਤੋਂ ਬਾਅਦ re-render trigger ਹੁੰਦਾ ਹੈ। ਜ਼ਿਆਦਾਤਰ ਮਾਮਲਿਆਂ ਵਿੱਚ, ਤੁਹਾਨੂੰ ਨੋਡ ਦੀ ਲੋੜ ਸਿਰਫ਼ ਇੱਕ imperative method ਜਾਂ ਮਾਪ (measurement) ਲਈ ਹੁੰਦੀ ਹੈ, ਇਸਨੂੰ ਵੱਖਰੇ ਤਰੀਕੇ ਨਾਲ render ਕਰਨ ਲਈ ਨਹੀਂ।

The Boolean Trap

ਜਦੋਂ ਹਰ flag ਨੂੰ ਆਪਣਾ ਵੱਖਰਾ hook ਮਿਲਦਾ ਹੈ, ਤਾਂ ਸਬੰਧਤ UI ਚਿੰਤਾਵਾਂ ਫੈਲਣ ਲੱਗਦੀਆਂ ਹਨ। ਤੁਸੀਂ ਅਜਿਹੇ components ਦੇਖਦੇ ਹੋ ਜਿੱਥੇ isLoading, isError, ਅਤੇ isSuccess ਨੂੰ ਤਿੰਨ ਵੱਖਰੇ booleans ਵਜੋਂ ਪਰਿਭਾਸ਼ਿਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਸਮੱਸਿਆ ਇਹ ਹੈ ਕਿ ਇਹ ਤਿੰਨ states ਸੁਤੰਤਰ ਨਹੀਂ ਹਨ। ਜੇਕਰ isLoading ਅਤੇ isSuccess ਦੋਵੇਂ true ਹਨ, ਤਾਂ ਤੁਹਾਡਾ UI ਇੱਕ ਅਸੰਭਵ ਸਥਿਤੀ ਵਿੱਚ ਹੈ, ਫਿਰ ਵੀ TypeScript ਅਤੇ React ਤੁਹਾਨੂੰ ਇਸਨੂੰ render ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦੇ ਦੇਣਗੇ।

ਸਬੰਧਤ state ਨੂੰ ਗਰੁੱਪ ਕਰਨ ਨਾਲ ਇਹਨਾਂ ਅਵੈਧ (invalid) ਸੁਮੇਲਾਂ ਨੂੰ ਰੋਕਿਆ ਜਾ ਸਕਦਾ ਹੈ। ਤਿੰਨ booleans ਦੀ ਬਜਾਏ, ਇੱਕ ਸਿੰਗਲ status string ਨੂੰ track ਕਰੋ: 'idle', 'loading', 'success', ਜਾਂ 'error'। ਇੱਕ ਸਮੇਂ ਵਿੱਚ ਸਿਰਫ਼ ਇੱਕ ਹੀ active ਹੋ ਸਕਦਾ ਹੈ, ਜੋ type level 'ਤੇ ਅਸੰਭਵ states ਨੂੰ ਖਤਮ ਕਰ ਦਿੰਦਾ ਹੈ। ਜੇਕਰ ਡੇਟਾ ਵਧੇਰੇ ਗੁੰਝਲਦਾਰ ਹੈ, ਤਾਂ ਇੱਕ object ਜਿਸ ਵਿੱਚ discriminated union ਹੋਵੇ, ਚੀਜ਼ਾਂ ਨੂੰ ਹੋਰ ਵੀ ਸਾਫ਼ ਕਰ ਦਿੰਦਾ ਹੈ। ਜਦੋਂ ਤੁਸੀਂ ਆਪਣੇ ਆਪ ਨੂੰ ਇੱਕੋ event handler ਦੇ ਅੰਦਰ ਕਈ useState calls ਨੂੰ ਅਪਡੇਟ ਕਰਦੇ ਹੋ ਪਾਉਂਦੇ ਹੋ, ਤਾਂ ਇਹ ਇੱਕ ਸੰਕੇਤ ਹੈ ਕਿ ਉਹ values ਇਕੱਠੀਆਂ ਹੋਣੀਆਂ ਚਾਹੀਦੀਆਂ ਹਨ।

##อีก ਇੱਕ useState ਦੀ ਬਜਾਏ useReducer ਦੀ ਵਰਤੋਂ ਕਰੋ

ਇੱਕ ਅਜਿਹਾ ਪੁਆਇੰਟ ਆਉਂਦਾ ਹੈ ਜਿੱਥੇ state updates ਇੱਕ whack-a-mole ਖੇਡ ਬਣ ਜਾਂਦੇ ਹਨ। ਤੁਸੀਂ ਇੱਕ ਹੀ function ਦੇ ਅੰਦਰ setA, ਫਿਰ setB, ਫਿਰ ਸ਼ਰਤ ਅਨੁਸਾਰ setC ਨੂੰ ਕਾਲ ਕਰਦੇ ਹੋ। ਉਸ code ਨੂੰ ਪੜ੍ਹਨ ਵਾਲੇ ਅਗਲੇ developer ਨੂੰ ਇਹ ਸਮਝਣ ਲਈ ਕਿ component ਅਸਲ ਵਿੱਚ ਕੀ ਕਰਦਾ ਹੈ, ਪੂਰੀ sequence ਨੂੰ ਟਰੇਸ ਕਰਨਾ ਪਵੇਗਾ।

useReducer ਇੱਥੇ ਸ਼ਾਨਦਾਰ ਕੰਮ ਕਰਦਾ ਹੈ। ਇਹ useState ਦੀ ਥਾਂ ਇਸ ਲਈ ਨਹੀਂ ਲੈਂਦਾ ਕਿਉਂਕਿ ਇਹ ਵਧੇਰੇ ਉੱਨਤ ਹੈ; ਇਹ useState ਦੀ ਥਾਂ ਇਸ ਲਈ ਲੈਂਦਾ ਹੈ ਕਿਉਂਕਿ logic ਦੀ ਮੰਗ ਹੈ। ਇੱਕ reducer ਇਹ ਨਿਰਧਾਰਤ ਕਰਦਾ ਹੈ ਕਿ state ਕਿਵੇਂ ਬਦਲਦੀ ਹੈ। Event handlers ਵਿੱਚ imperatives ਨੂੰ ਖਿਲਾਰਨ ਦੀ ਬਜਾਏ, ਤੁਸੀਂ ਇੱਕ ਇਰਾਦਾ (intention) dispatch ਕਰਦੇ ਹੋ: dispatch({ type: 'submitted' })। Reducer ਫੈਸਲਾ ਕਰਦਾ ਹੈ ਕਿ ਅਗਲੀ state ਕਿਹੋ ਜਿਹੀ ਹੋਵੇਗੀ। ਇਹ testing ਨੂੰ ਬਹੁਤ ਆਸਾਨ ਬਣਾਉਂਦਾ ਹੈ, ਕਿਉਂਕਿ ਤੁਹਾਡੀ state logic ਇੱਕ pure function ਹੈ। ਇਹ debugging ਨੂੰ ਵੀ ਆਸਾਨ ਬਣਾਉਂਦਾ ਹੈ, ਕਿਉਂਕਿ ਹਰ ਬਦਲਾਅ ਇੱਕ traceable action ਛੱਡਦਾ ਹੈ।

ਤੁਹਾਨੂੰ reducer ਦੀ ਵਰਤੋਂ ਨੂੰ ਜਾਇਜ਼ ठहराਉਣ ਲਈ Redux ਦੀ ਲੋੜ ਨਹੀਂ ਹੈ। ਜੇਕਰ ਤੁਹਾਡੇ ਕੋਲ ਤਿੰਨ ਜਾਂ ਇਸ ਤੋਂ ਵੱਧ state variables ਹਨ ਜੋ ਇਕੱਠੇ ਅਪਡੇਟ ਹੁੰਦੇ ਹਨ, ਜਾਂ ਜੇਕਰ ਤੁਹਾਡੀ ਅਗਲੀ state ਪਿਛਲੀ state 'ਤੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਨਿਰਭਰ ਕਰਦੀ ਹੈ, ਤਾਂ ਇੱਕ reducer component ਨੂੰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਸਰਲ ਬਣਾ ਦਿੰਦਾ ਹੈ।

State ਅਸਲ ਵਿੱਚ ਕਿੱਥੇ ਰਹਿੰਦੀ ਹੈ

ਕਦੇ-ਕਦੇ ਮਸਲਾ ਇਹ ਨਹੀਂ ਹੁੰਦਾ ਕਿ ਤੁਸੀਂ state ਨੂੰ ਕਿਵੇਂ ਸਟੋਰ ਕਰਦੇ ਹੋ, ਬਲਕਿ ਇਹ ਹੁੰਦਾ ਹੈ ਕਿ ਕਿੱਥੇ। ਇੱਕ ਆਮ ਗਲਤੀ state ਨੂੰ ਸਿਰਫ਼ ਇਸ ਲਈ parent ਵਿੱਚ hoist ਕਰਨਾ ਹੈ ਕਿਉਂਕਿ ਸ਼ਾਇਦ ਇਸਦੀ ਲੋੜ ਕਿਤੇ ਹੋਰ ਪੈ ਸਕਦੀ ਹੈ। ਜੇਕਰ ਸਿਰਫ਼ ਇੱਕ leaf component ਹੀ state ਦੇ ਕਿਸੇ ਹਿੱਸੇ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ, ਤਾਂ ਇਸਨੂੰ ਉੱਥੇ ਹੀ ਰੱਖੋ। ਇਹ colocation ਹੈ, ਅਤੇ ਇਹ ਬਦਲਾਅ ਦੇ ਪ੍ਰਭਾਵ (blast radius) ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ। Parent ਨੂੰ re-render ਨਾ ਕਰੋ ਕਿਉਂਕਿ ਇੱਕ child ਖੁੱਲ੍ਹ ਗਿਆ...