ما الذي يجب أن يعرفه كل مطور React عن الحالة State؟
تُعد State من أهم المفاهيم التي يجب على كل مطور React فهمها بعمق، لأنها المسؤولة عن إدارة البيانات المتغيرة داخل الواجهة. وفهم آلية عملها لا يساعدك فقط على كتابة كود أنظف، بل يجنبك أيضاً كثيراً من الأخطاء التي تظهر أثناء بناء التطبيقات التفاعلية.
في هذا المقال سنستعرض خمس حقائق أساسية عن State في React، مع أمثلة عملية واضحة تساعدك على بناء تصور تقني سليم، سواء كنت لا تزال في بداية الطريق أو تعمل بالفعل على مشاريع حقيقية.

1. تحديثات useState لا يتم دمجها تلقائياً
من أكثر النقاط التي تُربك المطورين عند الانتقال من المكونات المبنية على الأصناف Class Components إلى المكونات الدالية Function Components أن تحديثات الحالة في useState لا تُدمج تلقائياً عندما تكون الحالة عبارة عن كائن Object.
في المثال التالي يتم التعامل مع البريد الإلكتروني وكلمة المرور كمتغيري حالة منفصلين، وهذه طريقة واضحة وبسيطة في النماذج الصغيرة:
import React from "react";
export default function App() {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
return (
<form>
<input
name="email"
type="email"
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
/>
<input
name="password"
type="password"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
}
لكن أحياناً نفضّل جمع بيانات النموذج داخل كائن واحد باستخدام useState لمرة واحدة فقط. هنا تظهر المشكلة الشائعة:
import React from "react";
export default function App() {
const [state, setState] = React.useState({ email: '', password: '' });
function handleInputChange(e) {
setState({
[e.target.name]: e.target.value
});
}
return (
<form>
<input name="email" type="email" onChange={handleInputChange} />
<input name="password" type="password" onChange={handleInputChange} />
<button type="submit">Submit</button>
</form>
);
}
الخطأ هنا أن الاستدعاء السابق لـ setState يستبدل الكائن كاملاً، ولا يحتفظ تلقائياً بالقيم السابقة. أي أنك عند تعديل حقل واحد قد تفقد الحقول الأخرى.

الحل الصحيح هو دمج الحالة السابقة يدوياً باستخدام معامل النشر spread operator:
import React from "react";
export default function App() {
const [state, setState] = React.useState({ email: '', password: '' });
function handleInputChange(e) {
setState({
...state,
[e.target.name]: e.target.value
});
}
return (
<form>
<input name="email" type="email" onChange={handleInputChange} />
<input name="password" type="password" onChange={handleInputChange} />
<button type="submit">Submit</button>
</form>
);
}
الخلاصة هنا:
- إذا كانت الحالة بسيطة، فغالباً الأفضل استخدام عدة متغيرات حالة منفصلة.
- إذا استخدمت كائناً واحداً مع
useState، فعليك دمج القيم السابقة بنفسك. - لا تتوقع من
useStateأن يتصرف مثلthis.setStateفي المكونات الكلاسيكية.
2. تحديث الحالة يعيد التصيير، بينما useRef لا يفعل ذلك
ترتبط State في React مباشرة بعملية التصيير Render. فعندما تتغير الحالة، يعيد React بناء الجزء المعني من الواجهة ليعرض البيانات الجديدة. وهذه الفكرة جوهرية جداً: لو لم تحدث إعادة تصيير بعد تحديث الحالة، فلن يرى المستخدم أي تغيير على الشاشة.
لكن يجب الانتباه إلى أن إعادة التصيير لا تقتصر على المكون الأب فقط، بل قد تمتد إلى المكونات الأبناء أيضاً. وهذا قد يسبب عمليات تصيير غير ضرورية تؤثر على الأداء في بعض الحالات.
المثال التالي يوضح ذلك:
export default function App() {
const [skill, setSkill] = React.useState("");
const [skills, setSkills] = React.useState(["HTML", "CSS", "JavaScript"]);
function handleChangeInput(event) {
setSkill(event.target.value);
}
function handleAddSkill() {
setSkills(skills.concat(skill));
}
return (
<>
<input onChange={handleChangeInput} />
<button onClick={handleAddSkill}>Add Skill</button>
<SkillList skills={skills} />
</>
);
}
const SkillList = React.memo(({ skills }) => {
console.log("rerendering");
return (
<ul>
{skills.map((skill, i) => (
<li key={i}>{skill}</li>
))}
</ul>
);
});
في هذا المثال، كل ضغطة على لوحة المفاتيح داخل الحقل تؤدي إلى تحديث skill، وبالتالي إعادة تصيير المكون الأب App. وبدون استخدام React.memo فإن المكون SkillList سيُعاد تصييره أيضاً، حتى لو لم تتغير بياناته فعلياً.
لهذا يساعد React.memo على تقليل التصيير غير الضروري للمكونات التي لا تتغير مدخلاتها.
ومن جهة أخرى، هناك أداة مهمة هي useRef. هذا الخطاف يسمح لك بتخزين قيمة داخل الخاصية .current دون أن يؤدي تحديثها إلى إعادة التصيير:
import React from "react";
export default function App() {
const countRef = React.useRef(0);
function handleAddOne() {
countRef.current += 1;
}
return (
<>
<h1>Count: {countRef.current}</h1>
<button onClick={handleAddOne}>+ 1</button>
</>
);
}
رغم أن القيمة تتغير فعلاً داخل countRef.current، فإن الواجهة لن تُحدّث تلقائياً، لأن useRef لا يطلق إعادة تصيير.
استخدم useState عندما تريد أن يظهر التغيير للمستخدم، واستخدم useRef عندما تحتاج إلى حفظ قيمة بين التصييرات دون التأثير على الواجهة.
3. يجب أن تكون تحديثات الحالة غير قابلة للتعديل المباشر Immutable
واحدة من أهم قواعد العمل مع React هي عدم تعديل الحالة مباشرة. يجب تحديث الحالة فقط عبر الدالة المخصصة التي يعيدها useState، مثل setCount أو setState.
إذا حاولت تغيير القيمة المخزنة في الحالة بأسلوب JavaScript مباشر، فلن يتعرف React على التغيير بالشكل الصحيح، وغالباً لن تحدث إعادة تصيير كما تتوقع.
import React from 'react';
export default function App() {
const [count, setCount] = React.useState(0);
// Don't assign state to new (non-state) variables
const newCount = count;
// Don't directly mutate state
const countPlusOne = count + 1;
return (
<>
<h1>Count: {count}</h1>
</>
);
}
الفكرة الجوهرية هنا هي مبدأ immutability، أي أن بيانات الحالة يجب التعامل معها على أنها قيم جديدة عند التحديث، لا أن يتم العبث بالقيمة الأصلية ذاتها.
هذا المبدأ يمنحك عدة فوائد:
- يسهّل على
Reactاكتشاف التغييرات. - يجعل سلوك التطبيق أكثر قابلية للتوقع.
- يقلل من الأخطاء المعقدة التي يصعب تتبعها.
- يحسن من قابلية اختبار الكود وصيانته.
وسواء كنت تستخدم useState أو useReducer أو حتى مكتبات مثل Redux، فإن القاعدة نفسها تبقى ثابتة: لا تعدّل الحالة مباشرة، بل أنشئ قيمة جديدة ومررها عبر آلية التحديث الصحيحة.
4. تحديثات الحالة غير متزامنة ويتم جدولة تنفيذها
من الأخطاء الشائعة أن يظن المطور أن استدعاء setState أو setCount يعني أن القيمة ستتغير فوراً في السطر التالي مباشرة. في الواقع، React لا ينفذ التحديث بشكل لحظي دائماً، بل يقوم بجدولته ثم يحدد الوقت المناسب لتطبيقه وإعادة التصيير.
هذا السلوك مهم لتحسين الأداء، لأن React يحاول تنظيم التحديثات بدلاً من تنفيذ كل تغيير فوراً بطريقة قد تكون مكلفة.
لذلك من المهم أن تغيّر نموذجك الذهني:
setStateلا يغيّر الحالة حالاً بالضرورة.- هو يطلب من
Reactجدولة تحديث جديد. - موعد تطبيق التحديث الفعلي تحدده آلية التصيير الداخلية.
وهنا يظهر فرق مهم بين useState وuseRef:
- تحديث
useStateمجدول وقد لا يكون فورياً. - تحديث
useRefعلى.currentيحدث مباشرة من حيث القيمة، لكنه لا يعيد التصيير.
فهم هذه النقطة أساسي جداً، خاصة عندما تبني منطقاً يعتمد على القيمة السابقة للحالة أو عندما تستغرب أن الطباعة في console.log لا تُظهر القيمة الجديدة مباشرة بعد الاستدعاء.
5. الحالة القديمة Stale State قد تظهر بسبب closures
من أكثر المشكلات الدقيقة في React ما يُعرف باسم stale state، أي الاعتماد على نسخة قديمة من الحالة أثناء التحديث. ويحدث ذلك غالباً داخل closure، أي عندما تستخدم دالة قيمة من نطاق خارجي وتحتفظ بها لوقت لاحق.
يتضح هذا في المثال التالي الذي يستخدم setTimeout:
import React from 'react';
export default function App() {
const [count, setCount] = React.useState(0);
function delayAddOne() {
setTimeout(() => {
setCount(count + 1);
}, 1000);
}
return (
<>
<h1>Count: {count}</h1>
<button onClick={delayAddOne}>+ 1</button>
</>
);
}
المشكلة هنا أن الدالة داخل setTimeout تلتقط قيمة count القديمة وقت إنشائها، وليس بالضرورة أحدث قيمة بعد النقرات المتعددة.

الحل الأفضل هو استخدام التحديث الدالي functional update، بحيث تمرر دالة إلى setCount تستقبل القيمة السابقة الحقيقية ثم تعيد القيمة الجديدة:
import React from 'react';
export default function App() {
const [count, setCount] = React.useState(0);
function delayAddOne() {
setTimeout(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
}
return (
<div>
<h1>Count: {count}</h1>
<button onClick={delayAddOne}>+ 1</button>
</div>
);
}
بهذه الطريقة لن تعتمد على القيمة القديمة داخل closure، بل على آخر قيمة موثوقة تديرها React لحظة تنفيذ التحديث.
استخدم هذا الأسلوب خصوصاً في الحالات التالية:
- عند التحديث اعتماداً على القيمة السابقة.
- عند وجود
setTimeoutأوsetInterval. - عند التعامل مع أحداث متتابعة وسريعة.
- عند تنفيذ أكثر من تحديث متقارب على نفس الحالة.
أفضل ممارسات عملية لإدارة State في React
- قسّم الحالة إلى أجزاء منطقية بدلاً من تجميع كل شيء في كائن واحد بلا حاجة.
- استخدم
useStateللبيانات التي تؤثر على العرض. - استخدم
useRefللقيم التي تريد حفظها دون إعادة تصيير. - لا تعدّل الكائنات أو المصفوفات مباشرة، بل أنشئ نسخة جديدة.
- اعتمد على التحديث الدالي عند استخدام القيمة السابقة للحالة.
- راقب التصييرات غير الضرورية واستخدم
React.memoعند الحاجة.
الخلاصة التقنية
فهم State في React لا يقتصر على معرفة طريقة استخدام useState فقط، بل يشمل إدراك طبيعة التحديثات، وعلاقتها بإعادة التصيير، وخطورة التعديل المباشر، واحتمالات الوقوع في مشكلة stale state. من الناحية التقنية، المطور المحترف هو من يختار الأداة المناسبة بين useState وuseRef ويدير التحديثات بطريقة غير مباشرة وآمنة وقابلة للتوسع. وكلما كان فهمك لهذه التفاصيل أعمق، أصبحت تطبيقاتك أكثر استقراراً وأفضل أداءً وأسهل صيانة.