React📅 2026-10-15⏱ 8 دقائق قراءة⚛️ مقال 6 من 10

useEffect في React — دليل شامل للتأثيرات الجانبية 2026

دليل عملي مفصل لـ useEffect في React — التأثيرات الجانبية، دورة الحياة، التنظيف، مع تمارين وحلول

في المقال السابق، تعلمت الأحداث والنماذج. الآن سنتعلم useEffect — وهو Hook يسمح بتشغيل تأثيرات جانبية (Side Effects) داخل المكونات.

في هذا الدليل العملي، سنأخذك خطوة بخطوة لفهم useEffect ومتى تستخدمه، مع تمارين وحلول.

ما هو Side Effect؟

Side Effect (تأثير جانبي) هو أي شيء يحدث خارج المكوّنة:

  • طلب API (Fetch).
  • تعديل DOM مباشرة.
  • تعيين مؤقت (setTimeout).
  • الاشتراك في event.
  • تعديل document.title.

⚠️ مشكلة: لا يمكن وضع هذه العمليات داخل render (سيسبب مشاكل).

✅ الحل: استخدم useEffect.

لماذا نحتاج useEffect؟

دورة حياة المكوّنة:

1. Mount (التركيب)     → useEffect بدون dependencies
2. Update (التحديث)    → useEffect مع dependencies
3. Unmount (الإزالة)   → useEffect مع cleanup

الصيغة الأساسية

import { useEffect } from "react";

useEffect(() => {
  // الكود الذي ينفذ
  return () => {
    // دالة التنظيف (اختياري)
  };
}, [dependencies]);

حالات useEffect

1. بدون dependencies — ينفذ بعد كل render

useEffect(() => {
  console.log("ينفذ بعد كل render");
});

⚠️ استخدم بحذر — قد يسبب حلقات لا نهائية.

2. مع مصفوفة فارغة — ينفذ مرة واحدة (Mount)

useEffect(() => {
  console.log("ينفذ مرة واحدة فقط");
}, []);

الاستخدام: جلب بيانات أولية، إعداد مكتبة، ...

3. مع dependencies — ينفذ عند تغير القيم

useEffect(() => {
  console.log("ينفذ عندما يتغير count");
}, [count]);

الاستخدام: الاستجابة لتغير قيمة معينة.

دالة التنظيف (Cleanup)

useEffect(() => {
  const timer = setInterval(() => {
    console.log("tick");
  }, 1000);

  // دالة التنظيف:
  return () => {
    clearInterval(timer);
  };
}, []);

⚠️ مهم: دالة التنظيف:

  • تُنفذ عند إزالة المكوّنة.
  • أو قبل إعادة تنفيذ useEffect.

أمثلة عملية

1. تعديل عنوان الصفحة

function PageTitle() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `العدد: ${count}`;
  }, [count]);

  return <button onClick={() => setCount(count + 1)}>+1</button>;
}

كل نقرة تُحدّث عنوان الصفحة!

2. المؤقت (Timer)

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds((s) => s + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return <p>مرّ {seconds} ثانية</p>;
}

3. جلب بيانات من API

function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
        setLoading(false);
      })
      .catch((err) => {
        setError(err.message);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>جاري التحميل...</p>;
  if (error) return <p>خطأ: {error}</p>;

  return (
    <ul>
      {users.map((user: any) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

4. الاعتماد على متغير

function SearchUsers() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (query === "") {
      setResults([]);
      return;
    }

    const timer = setTimeout(() => {
      fetch(`https://api.example.com/search?q=${query}`)
        .then((res) => res.json())
        .then(setResults);
    }, 500);

    return () => clearTimeout(timer);
  }, [query]);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <ul>{results.map((r: any) => <li key={r.id}>{r.name}</li>)}</ul>
    </div>
  );
}

الفكرة: Debouncing — لا يرسل الطلب إلا بعد 500 مللي ثانية من التوقف عن الكتابة.

5. الاشتراك في event

function WindowSize() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);

    window.addEventListener("resize", handleResize);

    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return <p>عرض الشاشة: {width}px</p>;
}

useEffect مع TypeScript

1. جلب بيانات

interface User {
  id: number;
  name: string;
  email: string;
}

function Users() {
  const [users, setUsers] = useState<User[]>([]);

  useEffect(() => {
    async function fetchUsers() {
      const res = await fetch("https://jsonplaceholder.typicode.com/users");
      const data: User[] = await res.json();
      setUsers(data);
    }
    fetchUsers();
  }, []);

  return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}

2. دالة التنظيف

useEffect(() => {
  const timer: NodeJS.Timeout = setTimeout(() => {
    console.log("بعد ثانية");
  }, 1000);

  return () => clearTimeout(timer);
}, []);

useEffect vs useLayoutEffect

المعيارuseEffectuseLayoutEffect
التوقيتبعد الرسمقبل الرسم
الأداء✅ أفضل⚠️ يحجب الرسم
الاستخداممعظم الحالاتقياس DOM

القاعدة: استخدم useEffect دائماً، إلا إذا كنت تقيس DOM.

أخطاء شائعة

🔴 الخطأ 1: حلقة لا نهائية

// ❌ خطأ:
useEffect(() => {
  setCount(count + 1);  // تحديث state ← إعادة render ← useEffect ← ...
}, [count]);

الحل: لا تحدّث state الذي تعتمد عليه.

🔴 الخطأ 2: نسيان dependencies

// ❌ خطأ: لا يتحدث عند تغير count
useEffect(() => {
  document.title = `العدد: ${count}`;
}, []);  // ← count مفقود

// ✅ صحيح:
useEffect(() => {
  document.title = `العدد: ${count}`;
}, [count]);

🔴 الخطأ 3: نسيان دالة التنظيف

// ❌ خطأ: تسريب ذاكرة
useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  // لا يوجد cleanup
}, []);

// ✅ صحيح:
useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => clearInterval(timer);
}, []);

🔴 الخطأ 4: استخدام async مباشرة

// ❌ خطأ:
useEffect(async () => {
  const data = await fetch("/api");
}, []);

// ✅ صحيح:
useEffect(() => {
  async function fetchData() {
    const data = await fetch("/api");
  }
  fetchData();
}, []);

تمارين عملية

تمرين 1: عنوان ديناميكي

أنشئ مكوّنة تحدّث عنوان الصفحة بعدّاد.

الحل:

function TitleUpdater() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `العدد: ${count}`;
  }, [count]);

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

تمرين 2: مؤقت تنازلي

أنشئ مؤقتاً يبدأ من 10 وينتهي عند 0.

الحل:

function Countdown() {
  const [count, setCount] = useState(10);

  useEffect(() => {
    if (count === 0) return;
    const timer = setTimeout(() => setCount(count - 1), 1000);
    return () => clearTimeout(timer);
  }, [count]);

  return <p>{count === 0 ? "⏰ انتهى!" : count}</p>;
}

تمرين 3: جلب مستخدمين

اجلب قائمة مستخدمين من API واعرضها.

الحل:

function UsersList() {
  const [users, setUsers] = useState<any[]>([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then(setUsers);
  }, []);

  return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}

تمرين 4: عرض حجم الشاشة

اعرض عرض الشاشة وحدّثه عند تغيير الحجم.

الحل:

function WindowSize() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handler = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handler);
    return () => window.removeEventListener("resize", handler);
  }, []);

  return <p>العرض: {width}px</p>;
}

تمرين 5: العدّاد التلقائي

أنشئ عدّاداً يزيد تلقائياً كل ثانية.

الحل:

function AutoCounter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setCount((c) => c + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return <p>{count}</p>;
}

تمرين 6: البحث مع Debounce

أنشئ حقل بحث لا يرسل الطلب إلا بعد 500ms.

الحل:

function DebouncedSearch() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState<string[]>([]);

  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }

    const timer = setTimeout(() => {
      setResults([`نتيجة 1 لـ "${query}"`, `نتيجة 2 لـ "${query}"`]);
    }, 500);

    return () => clearTimeout(timer);
  }, [query]);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <ul>{results.map((r, i) => <li key={i}>{r}</li>)}</ul>
    </div>
  );
}

تمرين 7: تسجيل الدخول التلقائي

أنشئ مكوّنة تسجّل رسالة عند mount و unmount.

الحل:

function Logger() {
  useEffect(() => {
    console.log("🟢 تم التركيب");

    return () => {
      console.log("🔴 تم الإزالة");
    };
  }, []);

  return <p>افتح Console</p>;
}

تمرين 8: مراقبة الشبكة

أضف مستمعاً لحدث online و offline.

الحل:

function NetworkStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const online = () => setIsOnline(true);
    const offline = () => setIsOnline(false);

    window.addEventListener("online", online);
    window.addEventListener("offline", offline);

    return () => {
      window.removeEventListener("online", online);
      window.removeEventListener("offline", offline);
    };
  }, []);

  return <p>{isOnline ? "🟢 متصل" : "🔴 غير متصل"}</p>;
}

حل المشاكل الشائعة

🔴 المشكلة 1: حلقة لا نهائية

السبب: تحديث state داخل useEffect مع dependency عليه.

الحل: احذف dependency أو لا تحدّث state.

🔴 المشكلة 2: البيانات لا تُحدّث

السبب: dependency فارغ [].

الحل: أضف المتغيرات التي يعتمد عليها الكود.

🔴 المشكلة 3: تسريب ذاكرة

السبب: عدم تنظيف setInterval/setTimeout.

الحل: استخدم دالة التنظيف.

🔴 المشكلة 4: Warning: Can't perform a React state update on an unmounted component

السبب: تحديث state بعد إزالة المكوّنة.

الحل: استخدم flag:

useEffect(() => {
  let isMounted = true;

  fetch("/api").then((data) => {
    if (isMounted) setData(data);
  });

  return () => {
    isMounted = false;
  };
}, []);

جدول useEffect

الصيغةمتى ينفذ؟الاستخدام
useEffect(() => {})بعد كل render⚠️ نادراً
useEffect(() => {}, [])مرة واحدة (Mount)✅ جلب بيانات
useEffect(() => {}, [dep])عند تغير dep✅ الاستجابة
return () => {}عند Unmount✅ التنظيف

قائمة تحقق نهائية

المهمةالحالة
فهم Side Effects⬜
useEffect مع dependency فارغ⬜
useEffect مع dependencies⬜
دالة التنظيف (Cleanup)⬜
جلب بيانات من API⬜
Debouncing⬜
حل التمارين الثمانية⬜

ماذا بعد هذا المقال؟

الآن بعد أن أتقنت useEffect، أنت جاهز للمقال التالي:

  1. القوائم والشروط — العرض الديناميكي.
  2. React Router — التنقل بين الصفحات.
  3. Context API — إدارة الحالة العامة.

الخلاصة

في هذا المقال، تعلمت:

  • ✅ ما هو Side Effect ولماذا نحتاجه.
  • ✅ الصيغة الأساسية لـ useEffect.
  • ✅ الحالات الثلاث (Mount, Update, Unmount).
  • ✅ دالة التنظيف (Cleanup).
  • ✅ جلب بيانات من API.
  • ✅ Debouncing.
  • ✅ حل 8 تمارين عملية.

تذكر: useEffect قوي لكن لا تبالغ في استخدامه. أحياناً هناك حلول أفضل.

هل أتقنت useEffect؟ شاركنا في التعليقات!

📚 مقالات ذات صلة

React

مشروع React متكامل — تطبيق متجر إلكتروني 2026

مشروع عملي شامل لبناء تطبيق متجر إلكتروني بـ React و TypeScript — مع السلة، البحث، التصفية، والحفظ التلقائي

React

Context API في React — إدارة الحالة العامة 2026

دليل عملي مفصل لـ Context API في React — إنشاء Context، Provider، useContext، مع تمارين وحلول

React

React Router — التنقل بين الصفحات في React 2026

دليل عملي مفصل لـ React Router — التنقل، المسارات، الروابط، والمعاملات، مع تمارين وحلول