كيف تعمل الحالة في React؟ شرح عملي مع أمثلة برمجية واضحة
تُعد State من أكثر المفاهيم أهميةً وحساسيةً في React، وهي أيضاً من أكثر الأجزاء التي تُربك المبتدئين وحتى المطورين ذوي الخبرة. والسبب بسيط: لأن State هي المسؤولة عن البيانات المتغيرة داخل الواجهة، وأي فهم غير دقيق لها ينعكس مباشرة على سلوك التطبيق.
في هذا الدليل العملي، سنشرح كيف تعمل State في React خطوةً بخطوة، بدءاً من طريقة عرض البيانات على الشاشة، ثم لماذا لا يكفي تغيير القيم يدوياً، وبعدها ننتقل إلى استخدام setState، ونختم بطريقة إدارة الحالة داخل المكونات الدالية باستخدام Hooks وuseState.
كيف تعرض React البيانات داخل واجهة المستخدم؟
لكي تعرض React أي عنصر على الشاشة، فإنها تعتمد على الدالة ReactDOM.render(). هذه الدالة تستقبل عنصراً أو مكوناً، ثم تُدخله داخل عنصر موجود مسبقاً في الصفحة.
ReactDOM.render(element, container[, callback])
element: قد يكون عنصراً منHTMLأو شيفرةJSXأو مكوناً يعيدJSX.container: هو العنصر الذي سيتم حقن المحتوى داخله في الواجهة.callback: دالة اختيارية تُنفَّذ بعد اكتمال العرض أو إعادة العرض.
مثال أول: عرض عنصر واحد
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
ReactDOM.render(<h1>Welcome to React!</h1>, rootElement);
في هذا المثال، تعرض React عنواناً واحداً فقط داخل العنصر root.
مثال ثانٍ: عرض عدة عناصر معاً
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
ReactDOM.render(
<div>
<h1>Welcome to React!</h1>
<p>React is awesome.</p>
</div>,
rootElement
);
عندما نحتاج إلى أكثر من عنصر، نُغلفها داخل عنصر أب مثل <div>.
تخزين JSX في متغير لسهولة التنظيم
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
const content = (
<div>
<h1>Welcome to React!</h1>
<p>React is awesome.</p>
</div>
);
ReactDOM.render(content, rootElement);
هذا الأسلوب أفضل حين يصبح المحتوى أطول، لأنه يجعل الشيفرة أوضح وأسهل في القراءة والصيانة.
لماذا لا تتحدث الواجهة عند تغيير المتغيرات العادية؟
لنأخذ مثالاً بسيطاً على عدّاد:
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
let counter = 0;
const handleClick = () => {
counter++;
console.log("counter", counter);
};
const content = (
<div>
<button onClick={handleClick}>Increment counter</button>
<div>Counter value is {counter}</div>
</div>
);
ReactDOM.render(content, rootElement);

على الرغم من أن قيمة counter تتغير فعلاً عند الضغط على الزر، فإن الواجهة لا تتحدث. السبب هو أن ReactDOM.render() تم استدعاؤها مرة واحدة فقط عند تحميل الصفحة، لذلك لم تُطلب من React إعادة عرض القيم الجديدة.
إعادة العرض يدوياً
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
let counter = 0;
const handleClick = () => {
counter++;
console.log("counter", counter);
renderContent();
};
const renderContent = () => {
const content = (
<div>
<button onClick={handleClick}>Increment counter</button>
<div>Counter value is {counter}</div>
</div>
);
ReactDOM.render(content, rootElement);
};
renderContent();
هنا أنشأنا دالة اسمها renderContent() وطلبنا منها إعادة العرض بعد كل ضغطة. هذه الطريقة تنجح من الناحية التعليمية، لكنها ليست الأسلوب العملي الذي نعتمد عليه في تطبيقات React الحديثة.

هل إعادة العرض مكلفة دائماً؟
قد يبدو أن إعادة العرض المتكرر تعني إعادة بناء كامل DOM في كل مرة، لكن React تستخدم آلية Virtual DOM لتحديد ما تغيّر فعلياً، ثم تحدّث العناصر الضرورية فقط. لذلك فهي أكثر كفاءة مما قد يبدو لأول وهلة.

مع ذلك، لا يُعد استدعاء دالة إعادة العرض يدوياً عند كل تحديث أسلوباً مناسباً. لهذا السبب ظهر مفهوم State.
ما هي الحالة State في React؟
State هي كائن يحتوي على البيانات المتغيرة التي يحتاج المكون إلى تتبعها والتفاعل معها. كلما تغيّرت هذه البيانات بالطريقة الصحيحة، تُعيد React عرض المكون تلقائياً.
في React، نكتب الشيفرة داخل مكونات، وهناك طريقتان شهيرتان لإنشاء المكونات:
- المكونات الصنفية
Class Components - المكونات الدالية
Functional Components
سنبدأ بالمكونات الصنفية لفهم الأساس، ثم ننتقل لاحقاً إلى المكونات الدالية وHooks.
استخدام State داخل المكونات الصنفية
import React from "react";
import ReactDOM from "react-dom";
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.state.counter = this.state.counter + 1;
console.log("counter", this.state.counter);
}
render() {
const { counter } = this.state;
return (
<div>
<button onClick={this.handleClick}>Increment counter</button>
<div>Counter value is {counter}</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
اسم المكون هنا يبدأ بحرف كبير مثل Counter، وهذا مهم جداً لأن React تميّز بهذه الطريقة بين المكونات والعناصر العادية.
في constructor قمنا بالآتي:
- استدعاء
super(props). - تهيئة
this.stateبقيمة أولية. - ربط
thisمع الدالةhandleClickباستخدامbind().

المشكلة هنا أننا عدّلنا State مباشرة بهذه الصيغة:
this.state.counter = this.state.counter + 1;
وهذا خطأ شائع. لا يجب أبداً تعديل State مباشرة، لأن React لن تعتبر ذلك تحديثاً رسمياً يستحق إعادة العرض، كما أن هذا الأسلوب قد يسبب سلوكاً غير متوقع داخل التطبيق.
الصيغة الصحيحة: استخدام setState()
لتحديث الحالة بالشكل الصحيح، توفّر React الدالة setState():
setState(updater, [callback])
updater: يمكن أن يكون كائناً أو دالة.callback: دالة اختيارية تُنفّذ بعد اكتمال تحديث الحالة.
بمجرد استدعاء setState()، تقوم React بإعادة عرض المكون وأبنائه عند الحاجة، من دون الحاجة إلى استدعاء render يدوياً.
التحديث بالاعتماد على الحالة السابقة
handleClick() {
this.setState((prevState) => {
return { counter: prevState.counter + 1 };
});
console.log("counter", this.state.counter);
}
هنا استخدمنا دالة تمرّر لنا prevState، ثم أعدنا كائناً جديداً يحتوي على القيمة المحدّثة. هذا الأسلوب هو الأفضل عندما تعتمد القيمة الجديدة على القيمة القديمة.

ستلاحظ أن الواجهة تتحدث بشكل صحيح، لكن console.log() قد يطبع القيمة القديمة. السبب أن setState() تعمل بشكل غير متزامن asynchronous، أي إن التحديث لا يحدث فوراً في نفس السطر.
كيف تحصل على القيمة الجديدة بعد التحديث؟
handleClick() {
this.setState(
(prevState) => {
return { counter: prevState.counter + 1 };
},
() => console.log("counter", this.state.counter)
);
}

يمكن تمرير دالة ثانية إلى setState() تُنفّذ بعد اكتمال التحديث. هذه الطريقة مناسبة للاختبار السريع أو تسجيل القيم، لكن في التطبيقات العملية يُفضّل غالباً استخدام دورة الحياة المناسبة.
استخدام componentDidUpdate() بدلاً من callback
componentDidUpdate(prevProps, prevState) {
if (prevState.counter !== this.state.counter) {
console.log("counter", this.state.counter);
}
}
تُعد componentDidUpdate() أكثر ملاءمة عندما تريد تنفيذ منطق محدد بعد تغيّر الحالة أو props.
تبسيط كتابة المكونات الصنفية
الصيغة التقليدية باستخدام constructor وbind() قد تكون طويلة، لذلك يمكن استخدام خصائص الأصناف class properties لتقليل التعقيد.
state = { counter: 0 };
handleClick = () => {
this.setState((prevState) => {
return { counter: prevState.counter + 1 };
});
};
عند استخدام الدوال السهمية arrow functions لا نحتاج إلى bind()، لأن this تُفهم تلقائياً من سياق الصنف.
اختصار الإرجاع في دوال الأسهم
this.setState((prevState) => ({ counter: prevState.counter + 1 }));
بما أن الدالة تعيد كائناً واحداً فقط، يمكن اختصارها إلى سطر واحد. لاحظ أننا وضعنا الكائن بين قوسين دائريين حتى تميّزه JavaScript على أنه قيمة معادة وليس بداية جسم الدالة.
const add = (a, b) => {
return a + b;
};
const addShort = (a, b) => a + b;
متى تستخدم كائناً ومتى تستخدم دالة داخل setState()؟
يمكنك تمرير كائن مباشرة إلى setState() إذا كانت القيمة الجديدة لا تعتمد على القيمة السابقة.
class User extends React.Component {
state = { name: "Mike" };
handleChange = (event) => {
const value = event.target.value;
this.setState({ name: value });
};
render() {
const { name } = this.state;
return (
<div>
<input
type="text"
onChange={this.handleChange}
placeholder="Enter your name"
value={name}
/>
<div>Hello, {name}</div>
</div>
);
}
}

في هذا المثال لا نحتاج إلى prevState، لأننا نأخذ القيمة مباشرة من الحقل النصي، لذلك تمرير كائن يكفي تماماً.
أما إذا كانت القيمة الجديدة تعتمد على القيمة الحالية، فالأفضل تمرير دالة لتجنّب النتائج غير الدقيقة الناتجة عن الطبيعة غير المتزامنة لـ setState().
مشكلة شائعة عند استخدام كائن داخل setState()
handleClick = () => {
const { counter } = this.state;
this.setState({ counter: counter + 1 });
};
المثال السابق يبدو سليماً، وغالباً سيعمل في الحالات البسيطة.
لكن لاحظ المثال التالي:
handleClick = () => {
this.setState({ counter: 5 });
const { counter } = this.state;
this.setState({ counter: counter + 1 });
};

قد تتوقع أن تصبح القيمة 6، لكنها لا تفعل ذلك. السبب أن التحديث الأول لم يكتمل فوراً، ولذلك ما زلت تقرأ القيمة القديمة من this.state.
الحل الصحيح باستخدام prevState
handleClick = () => {
this.setState({ counter: 5 });
this.setState((prevState) => {
return { counter: prevState.counter + 1 };
});
this.setState((prevState) => {
return { counter: prevState.counter + 1 };
});
};

هنا ستُضبط القيمة أولاً إلى 5، ثم تزيد مرتين لتصبح 7. هذا يحدث لأن React تدمج التحديثات، وتستخدم prevState لحساب القيمة النهائية بدقة قبل إعادة العرض مرة واحدة.
الخلاصة العملية هنا:
- استخدم كائناً إذا كانت القيمة الجديدة مستقلة.
- استخدم دالة إذا كانت القيمة الجديدة مبنية على قيمة سابقة.
احذر من استخدام الحالة مباشرة بعد تحديثها
state = { isLoggedIn: false };
doSomethingElse = () => {
const { isLoggedIn } = this.state;
if (isLoggedIn) {
// do something different
}
};
handleClick = () => {
this.setState({ isLoggedIn: true });
doSomethingElse();
};
في هذا النمط، قد تتوقع أن تكون قيمة isLoggedIn قد أصبحت true مباشرة، لكن ذلك غير مضمون. لذلك يجب الانتباه دائماً عند تشغيل منطق يعتمد على الحالة مباشرة بعد استدعاء setState().
كيف تدمج React تحديثات الحالة تلقائياً؟
state = { counter: 0, username: "" };
handleOnClick = () => {
this.setState((prevState) => ({ counter: prevState.counter + 1 }));
};
handleOnChange = (event) => {
this.setState({ username: event.target.value });
};
عند تحديث counter لا تحتاج إلى إعادة تمرير username، والعكس صحيح. تقوم React بدمج خصائص الحالة داخلياً، ما يجعل التحديثات أبسط وأوضح.

استخدام المكونات الدالية في React
قبل ظهور Hooks، كانت المكونات الدالية تُستخدم غالباً لعرض الواجهة فقط من دون حالة داخلية أو دورات حياة. كانت تستقبل props وتعيد JSX.
const User = (props) => {
const { name, email } = props;
const { first, last } = name;
return (
<div>
<p>Name: {first} {last}</p>
<p>Email: {email}</p>
<hr />
</div>
);
};
في هذا المثال، المكون User يستقبل البيانات عبر props ويعرضها. لا نستخدم this هنا، وهذا أحد أسباب بساطة المكونات الدالية وسهولة اختبارها.
ومن المهم أن يبدأ اسم المكون بحرف كبير مثل User، لأن كتابة <user /> ستجعل React تتعامل معه كعنصر HTML عادي.
كيف تستخدم الحالة في المكونات الدالية مع React Hooks؟
ابتداءً من الإصدار 16.8.0، قدمت React مفهوم Hooks، وهو ما غيّر طريقة كتابة المكونات بشكل جذري. بفضل Hooks أصبح بالإمكان استخدام الحالة ودورات الحياة داخل المكونات الدالية بسهولة كبيرة.
أشهر Hook لإدارة الحالة هي useState.
مثال بصيغة المكونات الصنفية
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
state = { counter: 0 };
handleOnClick = () => {
this.setState(prevState => ({ counter: prevState.counter + 1 }));
};
render() {
return (
<div>
<p>Counter value is: {this.state.counter}</p>
<button onClick={this.handleOnClick}>Increment</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
التحويل إلى مكون دالي باستخدام useState
import React, { useState } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [counter, setCounter] = useState(0);
return (
<div>
<div>
<p>Counter value is: {counter}</p>
<button onClick={() => setCounter(counter + 1)}>Increment</button>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
في هذا المثال:
- استوردنا
useStateمنReact. - مررنا القيمة الابتدائية
0إلىuseState(). - استخدمنا تفكيك المصفوفة
array destructuringللحصول على:
counter: القيمة الحالية.setCounter: الدالة المسؤولة عن التحديث.
عند الضغط على الزر، نستدعي setCounter(counter + 1) لتحديث القيمة وإعادة عرض المكون تلقائياً.
الميزة الأهم هنا أن الشيفرة أصبحت أقصر وأوضح، وهذا سبب رئيسي في تفضيل Hooks في المشاريع الحديثة.
أفضل ممارسات لفهم الحالة في React
- لا تعدّل
stateمباشرة تحت أي ظرف. - استخدم
setState()أوsetCounter()لتحديث القيم. - إذا كانت القيمة الجديدة تعتمد على القيمة السابقة، استخدم دالة مع
prevState. - تذكر أن
setState()غير متزامنة، لذلك لا تعتمد على القيمة الجديدة فوراً في السطر التالي. - في المكونات الحديثة، يُفضَّل استخدام
Functional ComponentsمعHooks. - حافظ على وضوح أسماء الحالة والدوال مثل
counterوsetCounter.
الخلاصة التقنية
فهم State في React هو المفتاح الحقيقي لبناء واجهات تفاعلية مستقرة وقابلة للتوسع. الفكرة الجوهرية ليست فقط في تخزين البيانات، بل في معرفة متى وكيف تُحدَّث هذه البيانات بالطريقة التي تفهمها React وتستجيب لها بكفاءة. إذا أتقنت الفرق بين التحديث المباشر والتحديث عبر setState، وفهمت الطبيعة غير المتزامنة للتحديثات، ثم انتقلت إلى استخدام useState بوعي داخل المكونات الدالية، فستمتلك أساساً قوياً جداً لتطوير تطبيقات React احترافية وأكثر قابلية للصيانة.