React Hooks: شرح useState و useEffect بأمثلة عملية
تطوير الويب

React Hooks: شرح useState و useEffect بأمثلة عملية

دليل عملي لأهم hooks في React: useState لإدارة الحالة و useEffect للتعامل مع الآثار الجانبية. أمثلة كاملة بدون حشو.

م
مؤسس LahbabiGuide
4 دقائق قراءة
شارك:

لماذا Hooks؟

قبل React 16.8، كانت إدارة الحالة تتطلّب class components — كود طويل ومعقّد. الـ Hooks سمحت لنا باستخدام الحالة والدورة الحياتية في دوال بسيطة.

النتيجة: كود أقصر بـ30%، أسهل قراءةً، وأقلّ أخطاءً.

useState: إدارة الحالة

الاستخدام الأساسي

jsx
import { useState } from "react";

function Counter() {
  // [القيمة الحالية, دالة التحديث] = useState(القيمة الأولية)
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>العدد: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(0)}>إعادة</button>
    </div>
  );
}

كل نقرة تستدعي setCount، فيُعاد رسم المكوّن تلقائياً بالقيمة الجديدة.

مع النصوص والكائنات

useState تعمل مع أيّ نوع بيانات:

jsx
const [name, setName] = useState("");
const [user, setUser] = useState({ name: "", age: 0 });

// تحديث كائن — انسخه ثم عدّله
setUser({ ...user, age: 30 });

خطأ شائع: تعديل الكائن مباشرة بـ user.age = 30 — React لن يعيد الرسم لأنه لم يكتشف تغييراً في المرجع.

الشكل الدالي (Functional Update)

عندما يعتمد التحديث على القيمة السابقة، استخدم الشكل الدالي:

jsx
// ❌ قد يسبب مشاكل
setCount(count + 1);

// ✅ آمن دائماً
setCount((prev) => prev + 1);

هذا مهم خصوصاً في async/await أو setTimeout.

useEffect: الآثار الجانبية

"الآثار الجانبية" = أي شيء يخرج خارج حسابات المكوّن: جلب بيانات، اشتراكات، تعديل DOM مباشر، المؤقّتات.

المثال الأساسي

jsx
import { useState, useEffect } from "react";

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // يُنفَّذ بعد كل تحديث لـ userId
    fetch(`/api/users/${userId}`)
      .then((r) => r.json())
      .then(setUser);
  }, [userId]); // ← مصفوفة التبعيات

  if (!user) return <p>جارٍ التحميل...</p>;
  return <h1>{user.name}</h1>;
}

القاعدة: أيّ متغيّر تستخدمه داخل useEffect ويأتي من خارجه (props, state) يجب إضافته لمصفوفة التبعيات.

متى يعمل useEffect؟

| مصفوفة التبعيات | متى يعمل | |------|-----------| | [] (فارغة) | مرّة واحدة فقط بعد أول render | | [x] | بعد كل تغيّر في x | | غير موجودة | بعد كل render (نادراً ما نريد هذا) |

التنظيف (Cleanup)

إن بدأت شيئاً (اشتراك، timer)، نظّفه عند إلغاء المكوّن:

jsx
useEffect(() => {
  const timer = setInterval(() => {
    console.log("تكّ كل ثانية");
  }, 1000);

  // هذه الدالة تعمل عند إلغاء المكوّن أو قبل الـ effect التالي
  return () => clearInterval(timer);
}, []);

نسيان التنظيف = تسرّب ذاكرة (memory leak).

إعلان

قواعد Hooks

تذكّر دائماً:

  1. استخدمها في أعلى المكوّن فقط — لا داخل if/else/for/while.
  2. من Function Components فقط — ليس من classes أو دوال عادية.
  3. الترتيب ثابت — React يعتمد على ترتيب الاستدعاء.
jsx
// ❌ خطأ — useState داخل شرط
function Bad({ active }) {
  if (active) {
    const [x, setX] = useState(0); // خطأ!
  }
}

// ✅ صحيح
function Good({ active }) {
  const [x, setX] = useState(0);
  if (!active) return null;
  return <div>{x}</div>;
}

مثال متكامل: نموذج تسجيل

jsx
import { useState } from "react";

function SignupForm() {
  const [form, setForm] = useState({ email: "", password: "" });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  async function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    setError("");
    try {
      const res = await fetch("/api/signup", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(form),
      });
      if (!res.ok) throw new Error("فشل التسجيل");
      alert("تم بنجاح!");
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={form.email}
        onChange={(e) => setForm({ ...form, email: e.target.value })}
        placeholder="البريد"
      />
      <input
        type="password"
        value={form.password}
        onChange={(e) => setForm({ ...form, password: e.target.value })}
        placeholder="كلمة السر"
      />
      {error && <p style={{ color: "red" }}>{error}</p>}
      <button disabled={loading}>
        {loading ? "جارٍ..." : "تسجيل"}
      </button>
    </form>
  );
}

أخطاء شائعة

  • "Cannot update state on unmounted component": حدّث الحالة داخل useEffect مع تنظيف
  • حلقة لا نهائية: useEffect يُعدّل حالة بدون مصفوفة تبعيات — يُعاد الرسم فيعيد useEffect العمل
  • البيانات قديمة (stale closure): استخدم الشكل الدالي setX(prev => ...)

الأسئلة الشائعة

هل أحفظ Hooks في ملف واحد؟

لا — كل مكوّن له hooks خاصّة. يمكن إنشاء custom hooks (مثل useAuth, useFetch) لمشاركة المنطق بين المكوّنات.

ما الفرق بين useState و useReducer؟

useState للقيم البسيطة. useReducer عند وجود تحديثات معقّدة مترابطة (سلّة تسوق مثلاً).

هل يعمل useState مع TypeScript؟

نعم، وبشكل ممتاز. النوع يُستنتج تلقائياً من القيمة الأولية، أو يمكنك تحديده: useState<User | null>(null).

شارك:
المزيد من تطوير الويب
اقرأ أيضاً

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