أخطاء شائعة في React وكيفية تجنبها thumbnail

أخطاء شائعة في React وكيفية تجنبها

مقدمة

React أداة قوية لبناء واجهات تفاعلية، لكنها لا تمنعك من كتابة كود معقد أو بطيء أو صعب الصيانة. أغلب مشاكل React لا تبدأ من اختيار مكتبة خاطئة، بل من سوء فهم دورة التصيير، وطريقة تحديث الـ state، ومتى يجب عزل المنطق عن الواجهة.

في هذا المقال سنمر على أكثر الأخطاء التي تظهر في مشاريع React، وكيف تتجنبها بطريقة عملية.

1. استخدام useEffect لكل شيء

واحد من أشهر الأخطاء هو التعامل مع useEffect كأنه المكان الطبيعي لأي منطق داخل المكون. لكن useEffect مخصص لمزامنة المكون مع شيء خارج React، مثل طلب API، event listener، timer، أو التعامل مع DOM API.

إذا كنت تستطيع حساب قيمة من props أو state أثناء التصيير، فلا تحتاج إلى useEffect.

// غير مفضل
const [fullName, setFullName] = useState("");

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

الأفضل:

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

الفكرة بسيطة: لا تخزن قيمة يمكن اشتقاقها مباشرة. هذا يقلل bugs ويجعل الكود أسهل في القراءة.

2. تجاهل dependency array

عندما تستخدم useEffect، يجب أن تكون dependencies دقيقة. حذف dependency لإيقاف إعادة التنفيذ قد يخفي bug بدل أن يحله.

useEffect(() => {
  fetchUser(userId);
}, []);

في المثال السابق، إذا تغير userId فلن يتم جلب بيانات المستخدم الجديد. الأفضل:

useEffect(() => {
  fetchUser(userId);
}, [userId]);

إذا كان effect يعاد تشغيله أكثر من اللازم، فغالبا المشكلة في تصميم الدوال أو البيانات التي تمررها، وليس في dependency array نفسها.

3. استخدام index كـ key في القوائم

استخدام index كـ key قد يبدو بريئا، لكنه يسبب مشاكل عند الحذف أو إعادة الترتيب أو الإدخال في منتصف القائمة.

items.map((item, index) => (
  <TodoItem key={index} item={item} />
));

الأفضل استخدام معرف ثابت:

items.map((item) => (
  <TodoItem key={item.id} item={item} />
));

الـ key يخبر React أي عنصر تغير فعلا. عندما يكون غير ثابت، قد تجد input يحتفظ بقيمة عنصر آخر أو animation تعمل على العنصر الخطأ.

4. تحديث state اعتمادا على قيمة قديمة

إذا كان التحديث يعتمد على القيمة السابقة، استخدم functional update.

setCount(count + 1);
setCount(count + 1);

هذا لا يعني بالضرورة زيادة الرقم مرتين، لأن count قد تكون نفس القيمة في الاستدعائين. الأفضل:

setCount((prev) => prev + 1);
setCount((prev) => prev + 1);

هذه الطريقة أوضح وأكثر أمانا عندما يكون لديك عدة تحديثات متتالية.

5. وضع كل شيء في global state

ليس كل state يجب أن تكون في Redux أو Context أو Zustand. إذا كانت البيانات تخص مكونا واحدا أو جزءا صغيرا من الصفحة، اتركها محلية.

أمثلة مناسبة للـ local state:

  • حالة فتح modal.
  • قيمة input داخل form.
  • tab نشط داخل component.
  • loading خاص بزر معين.

أما global state فتكون مفيدة عندما تحتاج نفس البيانات في أماكن بعيدة من التطبيق، مثل بيانات المستخدم الحالي أو إعدادات اللغة أو theme.

6. خلط منطق العمل داخل واجهة المستخدم

عندما يكبر المكون ويبدأ يحتوي على validation، formatting، API calls، filtering، وحسابات كثيرة، يصبح صعب الاختبار والتعديل.

بدل أن تضع كل شيء داخل component واحد، افصل ما يمكن فصله:

  • دوال formatting في ملف utilities.
  • منطق API في service أو custom hook.
  • مكونات صغيرة مسؤولة عن عرض جزء محدد.

مثلا:

function ProductPrice({ price, discount }) {
  const finalPrice = calculateFinalPrice(price, discount);

  return <span>{formatCurrency(finalPrice)}</span>;
}

بهذه الطريقة يصبح component مسؤولا عن العرض، بينما الحسابات يمكن اختبارها واستخدامها في أكثر من مكان.

7. الإفراط في useMemo و useCallback

كثير من المطورين يستخدمون useMemo و useCallback تلقائيا ظنا أنها تجعل التطبيق أسرع. أحيانا يحدث العكس: تضيف تعقيدا بدون فائدة حقيقية.

استخدمهما عندما:

  • الحساب ثقيل فعلا ويتكرر كثيرا.
  • تمرر callback إلى مكون يستخدم React.memo.
  • لديك قياس أو ملاحظة واضحة لمشكلة أداء.

لا تستخدمهما فقط لأن "هذا أفضل". الأداء الجيد يبدأ من تصميم state والمكونات، وليس من تغليف كل شيء بـ memoization.

8. عدم التعامل مع حالات التحميل والخطأ

أي طلب بيانات يجب أن يفكر في ثلاث حالات على الأقل:

  • loading: البيانات لم تصل بعد.
  • error: حدث خطأ في الطلب.
  • success: البيانات جاهزة.

تجاهل هذه الحالات يجعل الواجهة تبدو مكسورة للمستخدم.

if (isLoading) return <Spinner />;
if (error) return <ErrorMessage message={error.message} />;
if (!data) return <EmptyState />;

return <UserProfile user={data} />;

الكود الواضح لحالات الواجهة يقلل المفاجآت ويحسن تجربة المستخدم.

9. مكونات ضخمة تفعل كل شيء

إذا كان component واحد يحتوي على مئات السطور، فغالبا هو يعرف أكثر مما يجب. ليس المطلوب تقسيم كل شيء إلى ملفات صغيرة بلا معنى، لكن اسأل نفسك:

  • هل يوجد جزء يمكن إعادة استخدامه؟
  • هل يوجد منطق يمكن فصله في hook؟
  • هل يوجد جزء بصري مستقل؟

المكون الجيد له مسؤولية واضحة. عندما تقرأ اسمه، يجب أن تتوقع ما يفعله.

10. تجاهل TypeScript أو Prop Validation

في المشاريع الكبيرة، الاعتماد على الذاكرة لمعرفة شكل البيانات مكلف. TypeScript يساعدك على اكتشاف أخطاء كثيرة قبل التشغيل.

type UserCardProps = {
  user: {
    id: string;
    name: string;
    avatarUrl?: string;
  };
};

وجود types واضحة يجعل refactoring أسهل ويقلل الأخطاء عند تغيير API أو إعادة استخدام المكونات.

الخلاصة العملية

React لا تحتاج إلى حيل كثيرة بقدر ما تحتاج إلى قرارات واضحة. ابدأ بالـ state المحلي، استخدم useEffect فقط عندما تحتاج مزامنة مع شيء خارجي، اختر key ثابتة، ولا تضف abstractions أو memoization إلا عندما يكون لها سبب واضح.

لو طبقت هذه القواعد، ستجد أن كود React أصبح أبسط، أسرع في الصيانة، وأقل عرضة للأخطاء التي تظهر في وقت متأخر من المشروع.

avatar

Ahmed Ashraf

Website developer

Date the article was published

منذ 3 شهر • Jul 2026

Tags

React
background

Download CV

Would you like to learn more about my skills and experience? You can download my CV to explore my projects and achievements in detail.

Contact Me
image

A Web developer passionate about building modern digital experiences

©2026 Ahmed Ashraf - All Rights Reserved.