ما هي useState؟
هي Hook من رياكت تسمح لك بإضافة حالة (state) داخل المكوّنات الدالّية (Function Components).
ترجّع مصفوفتين:
- القيمة الحالية للحالة
- دالة لتحديث الحالة
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0); // 0 هي القيمة الابتدائية
return (
<button onClick={() => setCount(count + 1)}>
عدّاد: {count}
</button>
);
}JSXالتوقيع (Signature)
const [state, setState] = useState<T>(initialState | () => T);JSXinitialStateقد تكون قيمة مباشرة أو دالة تُرجع القيمة الابتدائية (تُستدعى مرة واحدة فقط عند أول رندر).setStateإمّا تُمرّر لها قيمة جديدة أو دالة تحديث تعتمد على القيمة السابقة.
القواعد الذهبية (Rules)
- تُستدعى useState فقط في أعلى المكوّن (وليس داخل شروط/حلقات/دوال داخلية).
- ترتيب الاستدعاءات مهم؛ لا تغيّره بين الرندرات.
- لا تعدّل الحالة مباشرة (لا تعمل
state.push()على المصفوفات…)، بل أنشئ نسخة جديدة.
1) عدّاد بسيط (Basic Counter)
الكود
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);
return (
<button onClick={increment}>
عدّاد: {count}
</button>
);
}
export default Counter;
JSXالشرح
useState(0): بننشئ حالة اسمهاcountوقيمتها الابتدائية 0 + دالة تحديثsetCount.increment: استخدمنا Functional Update (prev => prev + 1) لضمان إن القيمة مبنية على آخر state حقيقية حتى لو صار batching.- في الـ
return: زر يعرض القيمة الحالية، وكل ضغطة تشغّلincrementفتزيد القيمة وتعيد الرندر.
2) حالة بدائية (Boolean Toggle)
الكود
import { useState } from "react";
function Toggle() {
const [on, setOn] = useState(false);
const toggle = () => setOn(prev => !prev);
return (
<button onClick={toggle}>
الحالة: {on ? "ON" : "OFF"}
</button>
);
}
export default Toggle;JSXالشرح
- الحالة من نوع Boolean تبدأ بـ
false. - دالة
toggleتقلب القيمة بالاعتماد على السابقة. - الواجهة تعكس النتيجة نصيًا.
3) حالة كائن (Object State) وتحديث حقول
الكود
import { useState } from "react";
function UserCard() {
const [user, setUser] = useState({ name: "", age: 0 });
const updateName = (e) => {
setUser(prev => ({ ...prev, name: e.target.value }));
};
const incrementAge = () => {
setUser(prev => ({ ...prev, age: prev.age + 1 }));
};
return (
<div>
<input placeholder="الاسم" value={user.name} onChange={updateName} />
<button onClick={incrementAge}>+ العمر</button>
<p>الاسم: {user.name} — العمر: {user.age}</p>
</div>
);
}
export default UserCard;JSXالشرح
- مهم: لا نعدل الكائن مباشرة. نأخذ نسخة بـ
...prevونغيّر الحقل المطلوب. updateName: يأخذ النص من الحقل ويحدثname.incrementAge: يزيدageبدون لمس بقية الحقول.
4) حالة مصفوفة (Array) — مهام: إضافة/حذف/تبديل
الكود
import { useState } from "react";
function Todos() {
const [text, setText] = useState("");
const [todos, setTodos] = useState([]);
const addTodo = () => {
if (!text.trim()) return;
setTodos(prev => [
...prev,
{ id: crypto.randomUUID(), text: text.trim(), done: false },
]);
setText("");
};
const toggleTodo = (id) => {
setTodos(prev =>
prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
);
};
const removeTodo = (id) => {
setTodos(prev => prev.filter(t => t.id !== id));
};
return (
<div>
<input
placeholder="أضف مهمة…"
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTodo()}
/>
<button onClick={addTodo}>إضافة</button>
<ul>
{todos.map(t => (
<li key={t.id}>
<label>
<input
type="checkbox"
checked={t.done}
onChange={() => toggleTodo(t.id)}
/>
<span style={{ textDecoration: t.done ? "line-through" : "none" }}>
{t.text}
</span>
</label>
<button onClick={() => removeTodo(t.id)}>حذف</button>
</li>
))}
</ul>
</div>
);
}
export default Todos;JSXالشرح
- إضافة: ننشئ عنصر جديد ونُلحقه بنسخة جديدة من المصفوفة (
...prev). - تبديل: نستخدم
mapلإنشاء نسخة جديدة؛ لو الـ id مطابق نقلبdone. - حذف: نستخدم
filterلاستبعاد العنصر المطلوب.
5) التهيئة الكسولة (Lazy Initialization)
الكود
import { useState } from "react";
function heavyCompute() {
// محاكاة حساب ثقيل
let s = 0;
for (let i = 0; i < 2_000_000; i++) s += i;
return s;
}
function LazyInit() {
const [value, setValue] = useState(() => heavyCompute());
return (
<div>
<p>قيمة مبدئية محسوبة: {value}</p>
<button onClick={() => setValue(v => v + 1)}>+1</button>
</div>
);
}
export default LazyInit;JSXالشرح
- مررنا دالة لـ
useStateبدل قيمة مباشرة، فتُستدعى مرة واحدة فقط عند أول render. - مناسب لما تكون الحسابات الابتدائية ثقيلة.
6) التحديثات المتتالية المضمونة (Functional Updates)
الكود
import { useState } from "react";
function DoubleIncrement() {
const [count, setCount] = useState(0);
const addTwo = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<p>القيمة: {count}</p>
<button onClick={addTwo}>زوّد 2</button>
</div>
);
}
export default DoubleIncrement;JSXالشرح
- استدعينا
setCountمرتين بصيغة تعتمد علىprev. - React قد يعمل batching، لكن بهذه الصيغة النتيجة مضمونة: +2.
7) إدارة نموذج (Form) بسيط
الكود
import { useState } from "react";
function LoginForm() {
const [form, setForm] = useState({ email: "", password: "" });
const onChange = (e) => {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
};
const onSubmit = (e) => {
e.preventDefault();
console.log("Submitting:", form);
};
return (
<form onSubmit={onSubmit}>
<input name="email" placeholder="Email" value={form.email} onChange={onChange} />
<input name="password" type="password" placeholder="Password" value={form.password} onChange={onChange} />
<button type="submit">دخول</button>
</form>
);
}
export default LoginForm;JSXالشرح
- حالة الكائن تحفظ حقول النموذج.
- كل تغيير يحدّث الحقل المناسب عبر
name. - عند الإرسال نمنع تحديث الصفحة (
preventDefault) ونتعامل مع البيانات.
8) مؤقّت بسيط باستخدام useEffect (Stopwatch)
الكود
import { useEffect, useState } from "react";
function Stopwatch() {
const [sec, setSec] = useState(0);
useEffect(() => {
const id = setInterval(() => setSec(prev => prev + 1), 1000);
return () => clearInterval(id); // تنظيف عند التفكيك
}, []);
return <div>{sec}s</div>;
}
export default Stopwatch;JSXالشرح
useEffectبدون تبعيات (مصفوفة فارغة) ⇒ يُشغَّل مرة بعد الرندر الأول.- ننشئ
setIntervalيحدّث الحالة كل ثانية. - تنظيف بـ
clearIntervalعند إزالة المكوّن لمنع تسريب مؤقتات.
9) تبديل الثيم (Theme Toggle)
الكود
import { useState } from "react";
function ThemeSwitcher() {
const [theme, setTheme] = useState("light");
const toggle = () => setTheme(t => (t === "light" ? "dark" : "light"));
return (
<button onClick={toggle}>
الثيم الحالي: {theme}
</button>
);
}
export default ThemeSwitcher;
JSXالشرح
- نخزّن نص الثيم في state.
- نقلب بين “light/dark” بدالة تعتمد على القيمة الحالية.
- يمكنك لاحقًا ربطها بـ
<html data-theme={theme}>أو كلاس CSS.
10) عدّاد بخطوة ديناميكية (Prop)
الكود
import { useState } from "react";
function FlexibleCounter({ step = 1 }) {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c - step)}>-{step}</button>
<span style={{ margin: "0 8px" }}>{count}</span>
<button onClick={() => setCount(c => c + step)}>+{step}</button>
</div>
);
}
export default FlexibleCounter;JSXالشرح
- نأخذ
stepمن props (بقيمة افتراضية 1). - كل زر يضيف/ينقص بالقيمة المعطاة دون تكرار منطق.
11) بحث وتصفية منتجات (Filter + Search)
الكود
import { useState } from "react";
function Products({ items }) {
const [query, setQuery] = useState("");
const [onlyInStock, setOnlyInStock] = useState(false);
const visible = items
.filter(p => p.name.toLowerCase().includes(query.toLowerCase()))
.filter(p => (onlyInStock ? p.inStock : true));
return (
<>
<input
placeholder="ابحث…"
value={query}
onChange={e => setQuery(e.target.value)}
/>
<label style={{ marginLeft: 8 }}>
<input
type="checkbox"
checked={onlyInStock}
onChange={e => setOnlyInStock(e.target.checked)}
/>
فقط المتوفر
</label>
<ul>
{visible.map(p => (
<li key={p.id}>
{p.name} {p.inStock ? "" : "(غير متوفر)"}
</li>
))}
</ul>
</>
);
}
export default Products;
JSXالشرح
- حالتان: نص البحث + فلتر التوفر.
- نشتق قائمة
visibleمنitems(لا نخزنها في state لأنها مشتقة). - أي تغيير يعيد الرندر ويطبق الفلاتر مباشرة.
12) أخطاء شائعة + التصحيح
الخطأ 1: تعديل الحالة مباشرة
// ❌ خطأ
user.name = "Ali";
setUser(user);
// ✅ صحيح
setUser(prev => ({ ...prev, name: "Ali" }));JSXالخطأ 2: استخدام قيمة قديمة في تحديثات متتالية
// ❌ خطأ
setCount(count + 1);
setCount(count + 1);
// ✅ صحيح
setCount(prev => prev + 1);
setCount(prev => prev + 1);JSXالخطأ 3: استدعاء useState داخل شرط/حلقة
- لازم تكون في أعلى المكوّن دائمًا، بنفس الترتيب بكل رندر.