أكثر تحذير يراه متعلم React في حياته. في React 19 يظهر في الـ Console هكذا (نسخناه من تطبيق Next.js 16 بـ React 19.2):
Each child in a list should have a unique "key" prop.
Check the render method of `NoKey`. See https://react.dev/link/warning-keys for more information.
وفي React 18 وما قبله يبدأ بكلمة Warning:. السطر الثاني أهم ما فيه: Check the render method of NoKey يخبرك بالمكوّن الذي فيه الـ map المسؤول، فلا تبحث في المشروع كله.
والأخطر من التحذير نفسه هو «الحل» الشائع 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: «هذا نفس العنصر القديم» ويبقي حالته... فتظهر ملاحظتك داخل مهمة أخرى!
جرّبنا هذا فعليًا في React 19: ثلاث مهام لكل منها حقل <input>، كتبنا «اشترِ من المخبز القريب» في حقل المهمة الأولى «شراء خبز»، ثم حذفناها:
| المفتاح | ما ظهر بعد حذف المهمة الأولى |
|---|---|
key={index} | «دفع الفاتورة» وفي حقلها: اشترِ من المخبز القريب |
key={task.id} | «دفع الفاتورة» وحقلها فارغ، كما يجب |
مع index، حذف العنصر الأول جعل «دفع الفاتورة» تأخذ المفتاح 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>
))}
3) المفاتيح مكرّرة فعلًا: إن اخترت حقلًا ليس فريدًا (اسم، أو id يتكرّر بعد دمج قائمتين)، فالرسالة تختلف:
Encountered two children with the same key, `1`. Keys should be unique so that components
maintain their identity across updates. Non-unique keys may cause children to be duplicated
and/or omitted — the behavior is unsupported and could change in a future version.
القيمة بين علامتي الاقتباس (هنا 1) هي المفتاح المكرّر، فابحث عنها في بياناتك. وإن كانت القائمة مدموجة من مصدرين بمعرّفات متداخلة، فاجعل المفتاح مركّبًا: key={`${item.source}-${item.id}`}.
استخدام مفيد للمفتاح: إعادة ضبط مكوّن
بما أن تغيير key يجعل React يهدم المكوّن ويبنيه من جديد بحالة فارغة، يمكنك استخدامه عمدًا خارج القوائم. مثلًا نموذج تعديل مستخدم يجب أن يُفرَّغ حين تختار مستخدمًا آخر:
<EditProfileForm key={selectedUser.id} user={selectedUser} />
كل مستخدم جديد يعني مفتاحًا جديدًا، فتبدأ حالة النموذج من الصفر، بدل كتابة useEffect يراقب user ويصفّر كل حقل يدويًا.
قائمة تحقّق سريعة
- لكل
mapيرندر عناصر:keyعلى الابن المباشر المعاد. - المصدر: id حقيقي ← حقل فريد ← UUID مولّد عند الإنشاء.
- index فقط لقائمة جامدة بلا حالة داخلية — وعند الشك لا.
Math.random()بالرندر ممنوع منعًا قاطعًا.- التحذير باقٍ رغم key؟ → الابن المباشر، أو Fragment مختصر. ورسالة
Encountered two children with the same keyتعني مفاتيح مكررة فعلًا. - اقرأ
Check the render method of ...لتعرف أي مكوّن تفتح.
الخلاصة
المفتاح ليس «إرضاءً للتحذير» بل هوية يبني عليها React قراراته — أعطه هوية حقيقية ثابتة من بياناتك وستختفي عائلة كاملة من الأخطاء الغامضة: الحقول المختلطة، والحالات الملتصقة بالعنصر الخطأ، والرندر الزائد.
رسّخ الأساس في درس القوائم من مسار React، وأكمل عائلة أخطاء React مع حل حلقة useEffect اللانهائية وحل خطأ Hydration.