اگر آپ نے React پر کچھ وقت گزارا ہے، تو آپ نے اپنے کنسول میں یہ پیلی وارننگ ضرور دیکھی ہوگی: “Each child in a list should have a unique ‘key’ prop.” یہ ایک شائستہ مشورے کی طرح لگتا ہے، لیکن درحقیقت React آپ کو خبردار کر رہا ہے کہ وہ آپ کی لسٹ کے آئٹمز میں فرق نہیں کر پا رہا۔ اگر آپ اسے نظر انداز کر دیں گے، تو آپ بالآخر ایک ایسا بگ (bug) پیدا کر دیں گے جسے دوبارہ پیدا کرنا (reproduce کرنا) انتہائی مشکل اور پریشان کن ہوگا—جیسے اسٹیٹ کا غلط رو (row) میں چلے جانا، ٹیکسٹ ان پٹس کا فوکس کھو دینا، یا غلط ایلیمنٹ پر اینیمیشنز کا چلنا۔
React کا رینڈرنگ انجن آپ کے UI کا پکسل بہ پکسل موازنہ نہیں کرتا۔ یہ آبجیکٹس کا ایک ہلکا پھلکا درخت (tree) بناتا ہے جسے ورچوئل DOM (virtual DOM) کہا جاتا ہے، پھر نئے درخت کا پرانے درخت سے موازنہ کرتا ہے، اور ریئل DOM کے لیے درکار تبدیلیوں کا کم سے کم مجموعہ معلوم کرتا ہے۔ جب آپ ایک لسٹ رینڈر کرتے ہیں، تو React اسے سگبلنگ (sibling) ایلیمنٹس کے ایک ایرے کے طور پر دیکھتا ہے۔ کیز (keys) کے بغیر، اس کے پاس یہ جاننے کا کوئی قابلِ اعتماد طریقہ نہیں ہوتا کہ کوئی آئٹم منتقل ہوا ہے، تبدیل ہوا ہے، یا ہٹا دیا گیا ہے۔ یہ ڈیفالٹ طور پر پوزیشن کے ذریعے میچ کرنے کی کوشش کرتا ہے، جو کہ ایک غیر مستحکم (fragile) طریقہ ہے۔ کیز مستحکم شناخت (stable identity) کے طور پر کام کرتی ہیں۔ وہ React کو بتاتی ہیں، "یہ ایلیمنٹ وہی ہے جو پہلے تھا، چاہے اب یہ کسی دوسری جگہ پر ہی کیوں نہ ہو۔" اگر آپ اس میں غلطی کرتے ہیں، تو آپ یقینی اپ ڈیٹس کے بدلے اندازوں پر انحصار کرنے لگتے ہیں۔
The Minimum Fix
یہ وارننگ عام طور پر map کال کے اندر ظاہر ہوتی ہے۔ آپ کو ایٹریٹر (iterator) سے واپس آنے والے ٹاپ لیول ایلیمنٹ پر key ایٹریبیوٹ کو ایک منفرد ویلیو دینی چاہیے۔
یہاں وہ پیٹرن ہے جو آپ ہر اس کوڈ بیس میں دیکھتے ہیں جو وارننگ کا باعث بنتا ہے:
const UserList = ({ users }) => {
return (
<ul>
{users.map((user) => (
<li>{user.name}</li>
))}
</ul>
);
};
React تین <li ٹیگز دیکھتا ہے اور اسے اندازہ نہیں ہوتا کہ کون سا کون سا ہے۔ اس کی درستگی کے لیے صرف ایک ایٹریبیوٹ کی ضرورت ہے:
const UserList = ({ users }) => {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
key کو براہ راست map کال بیک کے اندر ایلیمنٹ کو تفویض (assign) کیا جانا چاہیے۔ اگر آپ <li کو ایک الگ UserItem کمپوننٹ میں نکال لیتے ہیں، تب بھی کی (key) اسی جگہ ہونی چاہیے جہاں کمپوننٹ کو کال کیا گیا ہے:
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
UserItem کے اندر اس کے داخلی <div پر کی (key) رکھنے سے وارننگ ختم نہیں ہوگی اور نہ ہی یہ reconciliation کے عمل کو درست کرے گی۔ React ایٹریٹر کے ذریعے واپس آنے والے ایلیمنٹ پر کی (key) تلاش کرتا ہے۔
Why Index as a Key Is Dangerous
map کے دوسرے آرگومنٹ کا استعمال کر کے وارننگ کو ختم کرنا ایک پرکشش خیال ہو سکتا ہے:
{users.map((user, index) => (
<li key={index}>{user.name}</li>
))}
یہ کنسول کے شور کو تو ختم کر دیتا ہے، لیکن یہ بنیادی مسئلے کو حل نہیں کرتا۔ ایرے کے انڈیکس (indices) شناخت نہیں ہوتے۔ وہ صرف پوزیشنز ہیں، اور پوزیشنز بدل جاتی ہیں۔
فرض کریں تین صارفین کی ایک لسٹ اس ترتیب میں رینڈر کی گئی ہے:
- Alice (index 0)
- Bob (index 1)
- Charlie (index 2)
اگر آپ Alice کو ڈیلیٹ کر دیتے ہیں، تو Bob انڈیکس 0 پر آ جاتا ہے اور Charlie انڈیکس 1 پر چلا جاتا ہے۔ React نئے درخت کا پرانے درخت سے موازنہ کرتا ہے۔ وہ دیکھتا ہے کہ انڈیکس 0 پر اب Bob کا ڈیٹا ہے، اس لیے وہ موجودہ DOM نوڈ کو تبدیل (mutate) کر دیتا ہے جو پہلے Alice کو دکھا رہا تھا۔ اگر اس نوڈ پر فوکس تھا، تو کرسر پہلی رو (row) میں ہی رہتا ہے، لیکن ٹیکسٹ بدل کر Bob ہو جاتا ہے۔ اگر اس رو میں لوکل اسٹیٹ (local
ایک کم واضح جال React Fragments سے متعلق ہے۔ اگر آپ ڈیٹا پر میپ (map) کرتے ہیں اور کسی ویپر (wrapper) <div> کے بغیر متعدد سگبن (sibling) عناصر واپس کرنا چاہتے ہیں، تو آپ مختصر سنٹیکس (short syntax) کا استعمال کرنے کی کوشش کر سکتے ہیں:
{items.map((item) => (
<>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</>
))}
شارٹ ہینڈ <>...</> props کو سپورٹ نہیں کرتا، جس کا مطلب ہے کہ آپ اس کے ساتھ key نہیں لگا سکتے۔ ایسی صورت میں، مکمل واضح سنٹیکس (explicit syntax) پر منتقل ہو جائیں:
{items.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
React کو Fragment پر اس key کی ضرورت ہوتی ہے تاکہ وہ رینڈرز (renders) کے دوران اس جوڑے کو ایک واحد یونٹ کے طور پر ٹریک کر سکے۔
ایک اور باریک نکتہ: keys عام معنوں میں props نہیں ہوتے۔ اگر آپ <ListItem key={item.id} /> لکھتے ہیں، تو ListItem کمپوننٹ props.key کو نہیں پڑھ سکتا۔ React اسے اندرونی طور پر بک کیپنگ (bookkeeping) کے لیے استعمال کرتا ہے۔ اگر آپ کے کمپوننٹ کو اپنی منطق (logic) کے لیے واقعی اس شناختی نمبر (identifier) کی ضرورت ہے، تو اسے کسی دوسرے نام کے تحت الگ سے پاس کریں، جیسے کہ itemId۔
آپ کو محفوظ رکھنے کے لیے عملی اصول
- ڈیٹا بیس IDs کو ترجیح دیں۔ یہ منفرد، عددی یا اسٹرنگ پر مبنی، اور مستحکم ہوتی ہیں۔
- صرف کلائنٹ کے ڈیٹا کے لیے
uuidیاnanoidکا استعمال کریں۔ ID کو صرف ایک بار ریکارڈ بناتے وقت جنریٹ کریں، کمپوننٹ رینڈر کے اندر نہیں۔ - اگر لسٹ تبدیل ہو سکتی ہے تو کبھی بھی array index سے key اخذ نہ کریں۔ ترتیب دینا (sorting)، فلٹر کرنا (filtering)، اور حذف کرنا (deleting) بصری اور اسٹیٹ (state) کے بگ (bugs) پیدا کرے گا۔
- کبھی بھی
Math.random()،Date.now()، یا ایسی کوئی بھی ویلیو استعمال نہ کریں جو رینڈرز کے درمیان تبدیل ہوتی ہو۔ یہ غیر ضروری ان ماؤنٹنگ (unmounting) اور ری ماؤنٹنگ (remounting) کا باعث بنتی ہے۔ - یاد رکھیں کہ Fragments کو طویل فارم (long form) کی ضرورت ہوتی ہے اگر وہ
mapکے اندر ہوں اور انہیں key کی ضرورت ہو۔ - key کو
mapکے ذریعے واپس کیے گئے عنصر پر رکھیں، نہ کہ کسی چائلڈ کمپوننٹ کے اندر۔
اصل خلاصہ
key prop کوئی محض سجاوٹی lint rule نہیں ہے۔ یہ وہ طریقہ ہے جس سے React رینڈرز کے دوران شناخت (identity) کو برقرار رکھتا ہے۔ اسے ڈیٹا بیس ٹیبل میں پرائمری کی (primary key) کی طرح سمجھیں۔ جب وہ شناخت مستحکم ہوتی ہے، تو React عناصر کو درست طریقے سے منتقل، اپ ڈیٹ اور حذف کر سکتا ہے۔ جب یہ غائب یا غیر مستحکم ہو، تو آپ کو خراب UI اسٹیٹ اور سست ری کنسیلیشن (reconciliation) کی صورت میں اس کی قیمت چکانی پڑتی ہے۔ اسے ایک بار ڈیٹا لیئر پر ٹھیک کر لیں، اور آپ کی لسٹیں چاہے جتنی بھی بڑھیں یا تبدیل ہوں، وہ قابلِ پیش گوئی (predictably) طریقے سے کام کریں گی۔
