کیا آپ نے کبھی ایسا ٹول ٹپ (tooltip) بنایا ہے جو اوپر بائیں کونے سے اچانک اپنی صحیح جگہ پر چھلانگ لگاتا ہو؟ یا کوئی ایسا ماڈل (modal) جو اپنی صحیح جگہ پر سیٹ ہونے سے پہلے غلط سائز میں چمکتا ہو؟ وہ ایک سیکنڈ کے ہزارویں حصے کا خلل (glitch) دراصل لے آؤٹ فلکر (layout flicker) ہے۔ یہ تب ہوتا ہے جب React DOM کو پڑھتا ہے، ایک اصلاح (correction) کا حساب لگاتا ہے، اور اسٹیٹ (state) کو اپ ڈیٹ کرتا ہے، لیکن براؤزر پہلے ہی اسکرین پر پکسلز دکھانا شروع کر چکا ہوتا ہے۔ عام حل useEffect کو useLayoutEffect سے بدلنا ہے۔ یہ تبدیلی کام تو کرتی ہے، لیکن صرف تب جب آپ کو یہ ٹھیک سے معلوم ہو کہ براؤزر پائپ لائن کے اندر ہر ہک (hook) کب چلتا ہے۔
براؤزر پائپ لائن: Render، Commit، Paint
React ایک کمپوننٹ کو تین الگ الگ مراحل میں اپ ڈیٹ کرتا ہے۔ render مرحلے میں، React Virtual DOM بناتا ہے—یا دوبارہ بناتا ہے—اور فرق (diff) کا حساب لگاتا ہے۔ ابھی تک کوئی اصل پکسل تبدیلی نہیں ہوتی؛ یہ صرف میموری میں ہونے والا حساب کتاب ہے۔ اس کے بعد commit مرحلہ آتا ہے، جہاں React ان تبدیلیوں کو اصل DOM نوڈز پر لاگو کرتا ہے۔ اسٹائلز اپ ڈیٹ ہوتے ہیں، نوڈز شامل یا خارج کیے جاتے ہیں، اور ٹیکسٹ تبدیل ہوتا ہے۔
پھر براؤزر کام سنبھال لیتا ہے۔ paint مرحلے میں، براؤزر کا رینڈرنگ انجن لے آؤٹ جیومیٹری کا حساب لگاتا ہے اور اسکرین پر پکسلز ڈرا کرتا ہے۔ یہ ترتیب غیر متغیر (rigid) ہے۔ براؤزر کو پینٹ کرنے سے پہلے لے آؤٹ مکمل کرنا ہوتا ہے، اور اسے پینٹ مکمل کرنا ہوتا ہے اس سے پہلے کہ صارف کو کچھ نیا نظر آئے۔ commit اور paint کے درمیان کا وقفہ ملی سیکنڈز میں ہوتا ہے، لیکن یہ حقیقت ہے، اور یہی وہ وقفہ ہے جہاں useEffect اور useLayoutEffect ایک دوسرے سے الگ ہو جاتے ہیں۔
useEffect فلکر کیوں پیدا کرتا ہے
useEffect غیر ہم آہنگ (asynchronously) طور پر چلتا ہے، اور اس کا شیڈول براؤزر کے اسکرین پینٹ کرنے کے بعد چلنے کے لیے ہوتا ہے۔ DOM اپ ڈیٹ ہو جاتا ہے، پکسلز ڈرا ہو جاتے ہیں، اور پھر React آپ کے ایفیکٹ (effect) کو چلانے کے لیے واپس آتا ہے۔
تصور کریں کہ آپ ایک بٹن کے نیچے ڈراپ ڈاؤن مینو (dropdown menu) رینڈر کرتے ہیں۔ useEffect کے اندر، آپ buttonRef.current.getBoundingClientRect() کال کرتے ہیں، درست top اور left کوآرڈینیٹس کا حساب لگاتے ہیں، اور انہیں اسٹیٹ میں محفوظ کرتے ہیں۔ چونکہ useEffect پینٹ کے بعد چلتا ہے، اس لیے براؤزر ڈراپ ڈاؤن کو پہلے ہی اپنی ڈیفالٹ پوزیشن پر ڈرا کر چکا ہوتا ہے، شاید top: 0, left: 0 پر۔ صرف اس پینٹ کے بعد آپ کا ایفیکٹ اسٹیٹ کو اپ ڈیٹ کرتا ہے۔ React درست کوآرڈینیٹس کو commit کرتا ہے، اور براؤزر دوبارہ پینٹ کرتا ہے۔ صارف کو دو فریم نظر آتے ہیں: پہلے غلط پوزیشن، پھر صحیح والی۔ وہ بصری جھٹکا (visual snap) ہی وہ فلکر ہے جس سے ہر کوئی بچنا چاہتا ہے۔
ڈیٹا فیچنگ (data fetching)، API کالز، اینالیٹکس ٹریکنگ، یا ایونٹ لسنرز (event listeners) سیٹ اپ کرنے کے لیے، اس تاخیر سے کوئی فرق نہیں پڑتا۔ صارف کو اس سے کوئی فرق نہیں پڑتا کہ اینالیٹکس بیکن (analytics beacon) پینٹ کے چند ملی سیکنڈ بعد چلتا ہے۔ درحقیقت، غیر بصری کاموں کو پینٹ کے بعد تک روکنا ابتدائی رینڈر کو ریسپونسو (responsive) رکھتا ہے۔ لیکن لے آؤٹ پر منحصر اصلاحات کے لیے، useEffect بہت دیر کر دیتا ہے۔
useLayoutEffect پینٹ کو کیسے روکتا ہے
useLayoutEffect ہم آہنگ (synchronously) طور پر چلتا ہے، فوراً اس وقت جب React DOM میں تبدیلی کرتا ہے لیکن اس سے پہلے کہ براؤزر کو لے آؤٹ کیلکولیٹ کرنے یا پکسلز پینٹ کرنے کا موقع ملے۔ یہ پینٹ پائپ لائن کو مکمل طور پر روک دیتا ہے۔
اگر آپ useLayoutEffect کے اندر وہی ڈراپ ڈاؤن پیمائش کرتے ہیں، تو ترتیب بدل جاتی ہے۔ React ابتدائی DOM اپ ڈیٹ کو commit کرتا ہے، آپ کا لے آؤٹ ایفیکٹ چلاتا ہے، اور آپ کی اسٹیٹ اپ ڈیٹ ایک ہم آہنگ ری-رینڈر (re-render) کو ٹرگر کرتی ہے۔ React درست کوآرڈینیٹس کو commit کرتا ہے، اور صرف اس کے بعد براؤزر پینٹ کرتا ہے۔ صارف کو ایک ہی فریم نظر آتا ہے، جو پہلے سے ہی درست ہوتا ہے۔
یہ بلاکنگ رویہ ایک فیچر بھی ہے اور ایک خطرہ بھی۔ چونکہ useLayoutEffect براؤزر کو مکمل ہونے تک پینٹ کرنے سے روکتا ہے، اس لیے اس کے اندر کوئی بھی بھاری حساب کتاب (heavy computation) UI کو منجمد (freeze) کر دیتا ہے۔ بلاک شدہ پینٹ کے چند درجن ملی سیکنڈز بھی صارف کو جھٹکے (jank) کے طور پر محسوس ہوتے ہیں۔ یہی وجہ ہے کہ React کی دستاویزات واضح طور پر کہتی ہیں کہ useEffect سے آغاز کریں اور صرف اس وقت useLayoutEffect کا استعمال کریں جب آپ واقعی ایسا فلکر دیکھیں جسے آپ برداشت نہ کر سکیں۔
ہر ہک کا استعمال کب کریں
آپ کا زیادہ تر لاجک (logic) useEffect میں ہونا چاہیے۔ اسے ان کاموں کے لیے استعمال کریں:
- API سے ڈیٹا فیچ کرنا
- سبسکرپشنز (subscriptions) یا ایونٹ لسنرز سیٹ اپ کرنا
- اینالیٹکس ایونٹس بھیجنا
- کوئی بھی ایسا سائیڈ ایفیکٹ (side effect) جو فوری طور پر لے آؤٹ کو نہ پڑھے یا تبدیل نہ کرے
useLayoutEffect کو ان آپریشنز کے لیے مخصوص رکھیں جنہیں صارف کے فریم دیکھنے سے پہلے DOM کو پڑھنا اور واپس لکھنا ضروری ہو:
- ایلیمنٹ کے ڈائمینشنز (dimensions) کی پیمائش کرنا، جیسے چوڑائی (width)، اونچائی (height)، یا اسکرول پوزیشن
- ٹول ٹپس، پاپ اوورز (popovers)، یا کانٹیکسٹ مینیوز (context menus) کے لیے کوآرڈینیٹس کا حساب لگانا
- بصری لے آؤٹ میں تبدیلیوں (layout shifts) کو روکنا جب بصری پوزیشن رینڈرڈ جیومیٹری پر منحصر ہو
اگر آپ کو یقین نہ ہو کہ کون سا منتخب کرنا ہے، تو ڈیفالٹ کے طور پر useEffect استعمال کریں۔ useLayoutEffect پر صرف تب جائیں جب آپ بصری عدم استحکام (visual instability) محسوس کریں۔ صرف یہ اصول ہی React ایپلی کیشنز کی اکثریت کو ہموار طریقے سے چلتے رہنے میں مدد دے گا۔
سرور سائیڈ رینڈرنگ (Server-Side Rendering) کا مسئلہ
اگر آپ Next.js، Remix، یا کوئی ایسا فریم ورک استعمال کرتے ہیں جو سرور پر React کو رینڈر کرتا ہے، تو آپ کو useLayoutEffect کے ساتھ ایک وارننگ ملے گی۔ چونکہ سرور کے پاس DOM نہیں ہوتا، اس لیے اس ہک کے پاس ناپنے کے لیے کچھ نہیں ہوتا۔ React آپ کو خبردار کرتا ہے کہ اسے براؤزر کے ماحول (environment) کی توقع تھی لیکن اسے ایسا کچھ نہیں ملا۔ Hydration کے دوران، یہ عدم مطابقت (mismatch) باریک غلطیاں بھی پیدا کر سکتی ہے کیونکہ سرور پر رینڈر شدہ مارک اپ اور کلائنٹ کا پہلا مطلوبہ رینڈر مختلف ہو سکتے ہیں۔
اس کا معیاری حل ایک isomorphic ہک ہے جو ماحول کی بنیاد پر صحیح ایفیکٹ کا انتخاب کرتا ہے:
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
اس ریپر (wrapper) کو کسی بھی ایسے کمپوننٹ میں استعمال کریں جسے DOM نوڈز کو ناپنا ہو لیکن وہ سرور رینڈرنگ کے دوران چل سکتا ہو۔ یہ وارننگ کو ختم کر دیتا ہے اور آپ کے سرور آؤٹ پٹ کو مستقل (consistent) رکھتا ہے۔
کارکردگی اور بہترین طریقے
چونکہ useLayoutEffect پینٹنگ (painting) کو روک دیتا ہے، اس لیے ہک کے باڈی (body) کو جتنا ممکن ہو سکے ہلکا رکھیں۔ لے آؤٹ ویلیو پڑھیں، درستگی کا حساب لگائیں، اور اسے واپس لکھ دیں۔ اس کے اندر ڈیٹا فیچ نہ کریں، بڑے آبجیکٹس کو پارس نہ کریں، یا مہنگے الگورتھم نہ چلائیں۔ یہاں بھاری کوڈ مین تھریڈ (main thread) کو روک دے گا اور آپ کا انٹرفیس منجمد (frozen) محسوس ہوگا۔
جب آپ عناصر (elements) کو ناپتے ہیں، تو document.getElementById کے بجائے React refs کا استعمال کریں۔ Refs آپ کے کمپوننٹ انسٹنس (instance) سے جڑے ہوتے ہیں، کوئری ٹرکس کے بغیر ری-رینڈرز میں برقرار رہتے ہیں، اور پورٹلز (portals) یا کنڈیشنل رینڈرنگ کے ساتھ قابل اعتماد طریقے سے کام کرتے ہیں۔ گلوبل آئی ڈی (Global ID) کی تلاش کمپوننٹ کی انکیپسولیشن (encapsulation) کو توڑ دیتی ہے اور عین اسی وقت null واپس کر سکتی ہے جب آپ کو ان کی ضرورت ہو۔
تقریباً ہر سائیڈ ایفیکٹ (side effect) کے لیے useEffect ہی بہترین ڈیفالٹ ہے۔ یہ براؤزر کو بغیر کسی مداخلت کے پینٹ کرنے دیتا ہے اور ڈیٹا، ایونٹس، اور بیرونی سنکرونائزیشن (synchronization) کو صفائی سے سنبھالتا ہے۔ useLayoutEffect ایک مخصوص مسئلے کے لیے ایک خصوصی ٹول ہے: پینٹ ہونے سے پہلے لے آؤٹ پڑھنا اور اسے واپس لکھنا۔ ان کے درمیان ٹائمنگ کے فرق پر مہارت حاصل کریں، اور آپ فلیکرز (flickers) کا پیچھا کرنے کے بجائے انہیں روکنا شروع کر دیں گے۔
اصل سبق: ہر چیز کے لیے useEffect سے آغاز کریں۔ جس لمحے آپ دیکھیں کہ کوئی ٹول ٹپ (tooltip) یا ماڈل (modal) خود کو درست کرنے سے پہلے غلط جگہ پر جھپکتا (blink) ہے، وہی آپ کا اشارہ ہے۔ useLayoutEffect پر سوئچ کریں، DOM کو ناپیں، اپنے لے آؤٹ کو ایڈجسٹ کریں، اور براؤزر کو ایک بار—درست طریقے سے پینٹ کرنے دیں۔
