أكثر تحذير يراه متعلم React في حياته:
Warning: Each child in a list should have a unique "key" prop.
والأخطر منه: «الحل» الشائع key={index} الذي يُسكت التحذير ويزرع
مكانه أخطاء صامتة أسوأ منه بكثير. لنفهم القصة مرة واحدة وللأبد.
لماذا يريد React مفتاحًا؟
عند كل إعادة رندر يقارن React قائمتك الجديدة بالقديمة ليلمس أقل قدر من DOM. المشكلة: كيف يعرف أن «العنصر الثاني الجديد» هو نفسه «الثالث القديم» انتقل مكانًا؟ المفتاح هو بطاقة الهوية:
{lessons.map((lesson) => (
<LessonCard key={lesson.id} lesson={lesson} />
))}
بهوية ثابتة، الحذف من وسط القائمة يعني «انزع صاحب الهوية X» — بدون هدم وإعادة بناء كل ما بعده، ومع الحفاظ على حالته الداخلية (نص مكتوب، أكورديون مفتوح).
لماذا index قنبلة موقوتة؟
// 😨 يُسكت التحذير... ويبدّل الهويات عند أي تغيير
{items.map((item, index) => <Row key={index} item={item} />)}
المشهد الكارثي الكلاسيكي — قائمة مهام فيها حقول إدخال:
- ثلاث مهام بمفاتيح 0، 1، 2 — كتبت ملاحظة في حقل المهمة الأولى.
- حذفت المهمة الأولى.
- المهمة الثانية ورثت المفتاح 0 — فيقول React: «هذا نفس العنصر القديم» ويبقي حالته... فتظهر ملاحظتك داخل مهمة أخرى!
نفس السم مع الترتيب والإضافة بالمقدمة: القيم تلتصق بالمواضع لا بالبيانات. أخطاء لا تصرخ في الـ console — تظهر أمام المستخدم مباشرة.
المفتاح الصحيح — بالترتيب التنازلي
| المصدر | مثال | الحكم |
|---|---|---|
| معرّف قاعدة البيانات | item.id | ✅ الذهب |
| حقل فريد طبيعي | user.email، post.slug | ✅ ممتاز |
| توليد عند الإنشاء | crypto.randomUUID() وقت إضافة العنصر | ✅ جيد للعناصر المحلية |
Math.random() داخل الرندر | مفتاح جديد كل رندر | ❌ كارثة: هدم وبناء دائم |
| index | الموضع | ⚠️ فقط للقوائم الجامدة (انظر الأسئلة) |
نقطة التوليد المحلي مهمة: الـ UUID يُصنع عند إنشاء العنصر (في معالج الإضافة) ويُخزن معه — وليس داخل الرندر وإلا صار مفتاحًا جديدًا كل مرة، وهو أسوأ من index نفسه.
فخّا «عندي key والتحذير باقٍ»
1) المفتاح ليس على الابن المباشر لـ map:
// ❌ على الوسم الداخلي — React لا يراه حيث يبحث
{items.map((it) => (
<div>
<span key={it.id}>{it.name}</span>
</div>
))}
// ✅ على أعلى عنصر معاد من الدالة
{items.map((it) => (
<div key={it.id}>
<span>{it.name}</span>
</div>
))}
2) Fragment مختصر لا يقبل مفتاحًا:
// ❌ <>...</> لا يحمل خصائص
{items.map((it) => <><dt>{it.term}</dt><dd>{it.def}</dd></>)}
// ✅ الصيغة الكاملة
import { Fragment } from "react";
{items.map((it) => (
<Fragment key={it.id}><dt>{it.term}</dt><dd>{it.def}</dd></Fragment>
))}
قائمة تحقّق سريعة
- لكل
mapيرندر عناصر:keyعلى الابن المباشر المعاد. - المصدر: id حقيقي ← حقل فريد ← UUID مولّد عند الإنشاء.
- index فقط لقائمة جامدة بلا حالة داخلية — وعند الشك لا.
Math.random()بالرندر ممنوع منعًا قاطعًا.- التحذير باقٍ رغم key؟ → الابن المباشر، أو مفاتيح مكررة فعلًا، أو Fragment مختصر.
الخلاصة
المفتاح ليس «إرضاءً للتحذير» بل هوية يبني عليها React قراراته — أعطه هوية حقيقية ثابتة من بياناتك وستختفي عائلة كاملة من الأخطاء الغامضة: الحقول المختلطة، والحالات الملتصقة بالعنصر الخطأ، والرندر الزائد.
رسّخ الأساس في درس القوائم من مسار React، وأكمل عائلة أخطاء React مع حل حلقة useEffect اللانهائية وحل خطأ Hydration.