تقریباً کوئی بھی React codebase کھولیں اور آپ کو ایک ہی طرح کا رجحان نظر آئے گا۔ ایک ڈویلپر کو کسی ویلیو کو ٹریک کرنے کی ضرورت ہوتی ہے، تو وہ useState کا سہارا لیتا ہے۔ کاؤنٹر چاہیے؟ useState۔ عارضی ان پٹ ویلیو؟ useState۔ ماڈل (modal) کو پلٹنے کے لیے بولین (boolean)؟ useState۔ جلد ہی، ایک ہی کمپوننٹ میں درجنوں الگ الگ ہکس (hooks) ہوتے ہیں، جن میں سے ہر ایک ڈیٹا کے ایک چھوٹے سے حصے کو مینیج کر رہا ہوتا ہے جس کی شاید رینڈرز (renders) کے دوران برقرار رہنے کی ضرورت بھی نہ ہو۔ اس کا نتیجہ شور مچاتے کوڈ (noisy code)، اضافی ری-رینڈرز (re-renders)، اور کمپوننٹ میں بکھرے ہوئے اسٹیٹ (state) کی صورت میں نکلتا ہے۔

یہ عادت قابلِ فہم ہے۔ useState وہ پہلا ہک ہے جو ہم میں سے اکثر سیکھتے ہیں، اور یہ کام کرتا ہے۔ لیکن کام کرنے کا مطلب یہ نہیں کہ یہ ہر جگہ فٹ بیٹھتا ہے۔ ہر ڈیٹا کے ٹکڑے کو ری ایکٹیو اسٹیٹ (reactive state) سمجھنا ایسے مسائل پیدا کرتا ہے جو صرف تب سامنے آتے ہیں جب کمپوننٹ بڑا ہو جاتا ہے۔

صرف اس لیے کہ یہ بدلتا ہے، اس کا مطلب یہ نہیں کہ اسے اسٹیٹ کی ضرورت ہے

وقت کے ساتھ بدلنے والا ہر ویری ایبل (variable) useState میں ہونا ضروری نہیں ہے۔ کچھ ویلیوز محض کسی دوسری چیز کا نتیجہ ہوتی ہیں جو آپ کے پاس پہلے سے موجود ہوتی ہیں۔ اگر آپ صرف اس لیے صارف کا پورا نام اسٹیٹ میں محفوظ کرتے ہیں کیونکہ آپ firstName اور lastName کو جوڑ رہے ہیں، تو اب آپ کے پاس سچائی کے دو ذرائع (two sources of truth) ہیں۔ جب پیرنٹ (parent) ری-رینڈر ہونے کی وجہ سے firstName اپ ڈیٹ ہوتا ہے، تو آپ کی fullName اسٹیٹ اس وقت تک پرانی (stale) رہتی ہے جب تک کہ آپ اسے سنک (sync) کرنے کے لیے کوئی دوسرا ایفیکٹ نہ چلائیں۔ آپ کو سنک ایفیکٹ کی ضرورت نہیں ہے۔ آپ کو ایک ڈیریوڈ ویلیو (derived value) کی ضرورت ہے۔

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

اسے رینڈر (render) کے دوران ہی کیلکولیٹ کریں۔ اگر یہ کیلکولیشن مہنگی ہے، تو اسے میموائز (memoize) کر لیں۔ لیکن اسے اپنا الگ useState ہک نہ دیں جب تک کہ صارف اس پورے نام کو ان حصوں سے آزادانہ طور پر ایڈٹ نہ کر سکے۔

یہی اصول فلٹرڈ لسٹوں (filtered lists) پر بھی لاگو ہوتا ہے۔ اگر آپ اسٹیٹ میں allItems اور filteredItems دونوں کو رکھتے ہیں، تو آپ نے اپنی مینٹیننس کی سطح کو دوگنا کر دیا ہے۔ رینڈر کے دوران فلٹر کریں۔ سورس ایرے (source array) اور فلٹر ٹیکسٹ کو اسٹیٹ میں رکھیں، پھر نظر آنے والی لسٹ کو ڈیریو (derive) کریں۔ اس سے یہ یقینی بنتا ہے کہ فلٹرڈ لسٹ کبھی بھی سورس کے ساتھ غیر ہم آہنگ (out of sync) نہ ہو۔

کچھ ویلیوز کو کبھی بھی ری-رینڈرز ٹرگر نہیں کرنے چاہئیں

useState خاص طور پر React کو یہ بتانے کے لیے موجود ہے کہ کچھ بدلا ہے اور DOM کو اپ ڈیٹ کرنے کی ضرورت ہو سکتی ہے۔ اگر کوئی ویلیو بدلتی ہے لیکن UI کا کوئی حصہ اس تبدیلی کی پرواہ نہیں کرتا، تو useRef ایک بہتر ٹول ہے۔

ٹائمرز (Timers) اور انٹرویلز (intervals) اس کی کلاسیکی مثال ہیں۔ اسٹیٹ میں setInterval آئی ڈیز کو محفوظ کرنے سے ہر بار ٹائمر شروع یا بند کرنے پر ری-رینڈر ہوتا ہے، حالانکہ صارف انٹرویل آئی ڈی کو نہیں دیکھ سکتا۔ ایک ref اس ویلیو کو React کو مطلع کیے بغیر محفوظ رکھتا ہے۔ یہی منطق پچھلے پراپس (previous props) کو ٹریک کرنے، پینٹ (paint) سے پہلے DOM نوڈز کو ناپنے، یا کسی کسٹم ہک کے لیے تازہ ترین کال بیک (callback) کو محفوظ کرنے پر بھی لاگو ہوتی ہے۔ خود سے پوچھیں: کیا اس ویلیو کا اسکرین پر نظر آنا ضروری ہے؟ اگر جواب نہیں ہے، تو غالباً اسے useState کی ضرورت نہیں ہے۔

DOM نوڈز خود بھی refs میں ہونے چاہئیں۔ اگرچہ آپ اسٹیٹ میں ایک DOM ایلیمنٹ محفوظ کر سکتے ہیں، لیکن ایسا کرنے سے ref کال بیک چلنے کے بعد ری-رینڈر ٹرگر ہوتا ہے۔ زیادہ تر معاملات میں، آپ کو نوڈ کی ضرورت صرف کسی امپیریٹو میتھڈ (imperative method) یا پیمائش (measurement) کے لیے ہوتی ہے، اسے مختلف طریقے سے رینڈر کرنے کے لیے نہیں۔

بولین ٹریپ (The Boolean Trap)

جب ہر فلیگ (flag) کو اپنا الگ ہک مل جاتا ہے تو متعلقہ UI معاملات پھیلنے لگتے ہیں۔ آپ ایسے کمپوننٹس دیکھتے ہیں جن میں isLoading، isError اور isSuccess تین الگ الگ بولینز (booleans) کے طور پر تعریف کیے گئے ہوں۔ مسئلہ یہ ہے کہ یہ تینوں اسٹیٹس آزاد نہیں ہیں۔ اگر isLoading اور isSuccess دونوں ٹرو (true) ہیں، تو آپ کا UI ایک ناممکن حالت میں ہے، پھر بھی TypeScript اور React آپ کو اسے رینڈر کرنے کی اجازت دے دیں گے۔

متعلقہ اسٹیٹ کو گروپ کرنے سے ان غلط مجموعوں (invalid combinations) سے بچا جا سکتا ہے۔ تین بولینز کے بجائے، ایک واحد اسٹیٹس اسٹرنگ (status string) کو ٹریک کریں: 'idle', 'loading', 'success', یا 'error'۔ ایک وقت میں صرف ایک ہی فعال ہو سکتا ہے، جو ٹائپ لیول پر ناممکن حالتوں کو ختم کر دیتا ہے۔ اگر ڈیٹا زیادہ پیچیدہ ہے، تو ایک ڈسکریمنٹڈ یونین (discriminated union) والا آبجیکٹ چیزوں کو مزید بہتر بنا دیتا ہے۔ جب آپ خود کو ایک ہی ایونٹ ہینڈلر کے اندر کئی useState کالز کو اپ ڈیٹ کرتے ہوئے پائیں، تو یہ ایک اشارہ ہے کہ وہ ویلیوز اکٹھی ہونی چاہئیں۔

useState کے بجائے useReducer کا استعمال کریں

ایک ایسا مقام آتا ہے جہاں اسٹیٹ اپ ڈیٹس "whack-a-mole" کا کھیل بن جاتی ہیں۔ آپ ایک ہی فنکشن کے اندر setA کو کال کرتے ہیں، پھر setB کو، اور پھر شرط کے مطابق setC کو۔ اس کوڈ کو پڑھنے والے اگلے ڈویلپر کو یہ سمجھنے کے لیے پوری ترتیب کا جائزہ لینا پڑے گا کہ کمپوننٹ اصل میں کیا کرتا ہے۔

یہاں useReducer بہترین ثابت ہوتا ہے۔ یہ useState کا متبادل اس لیے نہیں ہے کہ یہ زیادہ ایڈوانس ہے؛ بلکہ یہ اس لیے ہے کیونکہ لاجک اس کا تقاضا کرتی ہے۔ ایک ریڈیوسر (reducer) اس بات کو مرکزی حیثیت دیتا ہے کہ اسٹیٹ کیسے بدلتی ہے۔ ایونٹ ہینڈلرز میں مختلف امپیریٹو کمانڈز بکھیرنے کے بجائے، آپ ایک ارادہ (intention) ڈسپیچ (dispatch) کرتے ہیں: dispatch({ type: 'submitted' })۔ ریڈیوسر فیصلہ کرتا ہے کہ اگلی اسٹیٹ کیسی ہوگی۔ اس سے ٹیسٹنگ بہت آسان ہو جاتی ہے، کیونکہ آپ کی اسٹیٹ لاجک ایک پیور فنکشن (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