عند العمل على تطبيقات React، ستتعامل كثيرًا مع مفهوم State.
لكن المشكلة أن إدارة الحالة لا تعني فقط استخدام useState.
هناك Hooks أخرى تساعدك على تحسين الأداء، تنظيم الكود، تأجيل التحديثات الثقيلة،
مشاركة البيانات بين المكونات، أو التعامل مع منطق معقد.
في هذا المقال سنشرح أهم الأدوات المرتبطة بإدارة الحالة في React: useState, useMemo, useEffect, useCallback, useDeferredValue, useLayoutEffect, useTransition, useReducer,
و useContext.
ما هي State في React؟
الـ State هي البيانات التي تتغير داخل المكون، وعند تغييرها يقوم React بإعادة رسم الواجهة بناءً على القيمة الجديدة.
المشكلة
إذا كان لديك رقم عداد أو نص داخل input وتريد تحديث الواجهة عند تغييره، فلا يمكنك الاعتماد على متغير عادي، لأن React لن يعرف أن هناك تغييرًا حدث.
الحل
نستخدم useState لحفظ القيمة وإخبار React بإعادة التحديث عند تغييرها.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
}
export default Counter;
1. useState
يستخدم useState لإدارة الحالات البسيطة داخل المكون، مثل النصوص،
الأرقام، القيم المنطقية، أو القوائم الصغيرة.
المشكلة
لديك input وتريد تخزين القيمة التي يكتبها المستخدم وعرضها مباشرة في الصفحة.
الحل
نستخدم useState لتخزين قيمة input وتحديثها عند الكتابة.
import { useState } from "react";
function UserName() {
const [name, setName] = useState("");
return (
<div>
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Enter your name"
/>
<p>Hello, {name}</p>
</div>
);
}
export default UserName;
2. useMemo
يستخدم useMemo لحفظ نتيجة عملية حسابية ثقيلة حتى لا يتم تنفيذها
في كل مرة يتم فيها إعادة رسم المكون.
المشكلة
لديك قائمة منتجات وتريد حساب المنتجات الغالية، لكن العملية تتكرر مع كل تحديث حتى لو لم تتغير المنتجات.
الحل
نستخدم useMemo حتى يتم حساب النتيجة فقط عند تغير البيانات المطلوبة.
import { useMemo, useState } from "react";
function Products() {
const [search, setSearch] = useState("");
const products = [
{ id: 1, name: "Laptop", price: 1200 },
{ id: 2, name: "Mouse", price: 20 },
{ id: 3, name: "Phone", price: 900 },
];
const expensiveProducts = useMemo(() => {
console.log("Calculating expensive products...");
return products.filter((product) => product.price > 500);
}, [products]);
return (
<div>
<input
value={search}
onChange={(event) => setSearch(event.target.value)}
placeholder="Search..."
/>
{expensiveProducts.map((product) => (
<p key={product.id}>{product.name}</p>
))}
</div>
);
}
export default Products;
متى تستخدم useMemo؟
- عند وجود عمليات حسابية ثقيلة.
- عند فلترة أو ترتيب قوائم كبيرة.
- عند منع إعادة تنفيذ كود لا يحتاج للتنفيذ في كل Render.
3. useEffect
يستخدم useEffect لتنفيذ كود بعد عملية Render، مثل جلب البيانات
من API، التعامل مع localStorage، أو الاشتراك في أحداث معينة.
المشكلة
تريد جلب بيانات المستخدم من API عند فتح الصفحة.
الحل
نستخدم useEffect لتنفيذ عملية الجلب بعد تحميل المكون.
import { useEffect, useState } from "react";
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
async function getUser() {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
const data = await response.json();
setUser(data);
}
getUser();
}, []);
return (
<div>
{user ? <h2>{user.name}</h2> : <p>Loading...</p>}
</div>
);
}
export default UserProfile;
متى تستخدم useEffect؟
- عند جلب البيانات من API.
- عند تحديث عنوان الصفحة.
- عند التعامل مع localStorage.
- عند إضافة أو إزالة Event Listener.
4. useCallback
يستخدم useCallback لحفظ الدالة نفسها بين عمليات Render،
حتى لا يتم إنشاء دالة جديدة في كل مرة.
المشكلة
عند تمرير دالة إلى مكون ابن، قد يتم إعادة رسم المكون الابن بدون داعي لأن الدالة يتم إنشاؤها من جديد مع كل Render.
الحل
نستخدم useCallback للحفاظ على نفس مرجع الدالة طالما أن
الاعتماديات لم تتغير.
import { memo, useCallback, useState } from "react";
const Button = memo(function Button({ onClick }) {
console.log("Button rendered");
return <button onClick={onClick}>Increase</button>;
});
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const increaseCount = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
return (
<div>
<input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Type your name"
/>
<h2>Count: {count}</h2>
<Button onClick={increaseCount} />
</div>
);
}
export default Counter;
متى تستخدم useCallback؟
- عند تمرير دوال إلى مكونات تستخدم
memo. - عند وجود دالة داخل dependency array.
- عند محاولة تقليل عمليات Render غير الضرورية.
5. useDeferredValue
يستخدم useDeferredValue لتأجيل تحديث قيمة معينة عندما يكون التحديث
ثقيلًا على الواجهة.
المشكلة
لديك input يبحث داخل قائمة كبيرة، وعند الكتابة تصبح الواجهة بطيئة لأن React يحاول تحديث input ونتائج البحث الثقيلة في نفس الوقت.
الحل
نستخدم useDeferredValue لجعل قيمة البحث الثقيلة تتأخر قليلًا،
بينما يبقى input سريعًا في الاستجابة.
import { useDeferredValue, useMemo, useState } from "react";
function SearchList() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const items = Array.from({ length: 10000 }, (_, index) => {
return `Item ${index + 1}`;
});
const filteredItems = useMemo(() => {
return items.filter((item) =>
item.toLowerCase().includes(deferredQuery.toLowerCase())
);
}, [deferredQuery]);
return (
<div>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search..."
/>
<p>Searching for: {deferredQuery}</p>
{filteredItems.slice(0, 20).map((item) => (
<p key={item}>{item}</p>
))}
</div>
);
}
export default SearchList;
متى تستخدم useDeferredValue؟
- عند البحث داخل قوائم كبيرة.
- عند وجود تحديثات ثقيلة تؤثر على سرعة الكتابة.
- عندما تريد إعطاء أولوية أعلى لتفاعل المستخدم.
6. useLayoutEffect
يشبه useLayoutEffect الـ useEffect، لكنه يعمل قبل
أن يرسم المتصفح التغييرات على الشاشة.
المشكلة
تريد قياس حجم عنصر في الصفحة وتغيير شيء بناءً على هذا الحجم قبل أن يراه المستخدم، حتى لا يظهر وميض أو تغيير مفاجئ في التصميم.
الحل
نستخدم useLayoutEffect عندما نحتاج إلى قراءة أو تعديل layout
قبل ظهور النتيجة للمستخدم.
import { useLayoutEffect, useRef, useState } from "react";
function BoxSize() {
const boxRef = useRef(null);
const [boxWidth, setBoxWidth] = useState(0);
useLayoutEffect(() => {
if (boxRef.current) {
setBoxWidth(boxRef.current.offsetWidth);
}
}, []);
return (
<div>
<div
ref={boxRef}
style={{
width: "300px",
padding: "20px",
background: "#eee",
}}
>
This is a box
</div>
<p>Box width: {boxWidth}px</p>
</div>
);
}
export default BoxSize;
متى تستخدم useLayoutEffect؟
- عند قياس حجم عنصر قبل عرضه النهائي.
- عند تعديل مكان عنصر بناءً على حجمه.
- عند منع وميض بصري في الواجهة.
7. useTransition
يستخدم useTransition لتحديد أن تحديثًا معينًا ليس عاجلًا،
وبالتالي يمكن لـ React تأجيله حتى تبقى الواجهة سريعة.
المشكلة
عند الكتابة في input وتحديث قائمة كبيرة في نفس الوقت، قد يشعر المستخدم أن الكتابة بطيئة.
الحل
نستخدم useTransition لجعل تحديث القائمة أقل أولوية من تحديث input.
import { useState, useTransition } from "react";
function SearchWithTransition() {
const [inputValue, setInputValue] = useState("");
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();
function handleChange(event) {
const value = event.target.value;
setInputValue(value);
startTransition(() => {
const newList = Array.from({ length: 5000 }, (_, index) => {
return `${value} - Result ${index + 1}`;
});
setList(newList);
});
}
return (
<div>
<input
value={inputValue}
onChange={handleChange}
placeholder="Search..."
/>
{isPending && <p>Updating list...</p>}
{list.slice(0, 20).map((item) => (
<p key={item}>{item}</p>
))}
</div>
);
}
export default SearchWithTransition;
الفرق بين useTransition و useDeferredValue
useTransition يستخدم عندما تتحكم أنت في عملية تحديث State وتريد
جعل هذا التحديث أقل أولوية.
أما useDeferredValue فيستخدم عندما لديك قيمة جاهزة وتريد نسخة
مؤجلة منها.
8. useReducer
يستخدم useReducer عندما تصبح إدارة الحالة معقدة، أو عندما يكون
لديك أكثر من عملية تغير نفس الحالة.
المشكلة
لديك عداد يحتوي على أكثر من Action مثل زيادة، تقليل، وتصفير. استخدام useState قد يصبح غير منظم إذا زاد المنطق.
الحل
نستخدم useReducer لوضع منطق تحديث الحالة في دالة واحدة واضحة.
import { useReducer } from "react";
const initialState = {
count: 0,
};
function reducer(state, action) {
switch (action.type) {
case "increase":
return { count: state.count + 1 };
case "decrease":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
default:
return state;
}
}
function CounterReducer() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h2>Count: {state.count}</h2>
<button onClick={() => dispatch({ type: "increase" })}>
Increase
</button>
<button onClick={() => dispatch({ type: "decrease" })}>
Decrease
</button>
<button onClick={() => dispatch({ type: "reset" })}>
Reset
</button>
</div>
);
}
export default CounterReducer;
متى تستخدم useReducer؟
- عندما تكون الحالة عبارة عن Object يحتوي على أكثر من قيمة.
- عندما يوجد أكثر من Action لتحديث الحالة.
- عندما يصبح استخدام
useStateغير منظم.
9. useContext
يستخدم useContext لمشاركة البيانات بين المكونات بدون تمريرها
يدويًا عبر props من مكون إلى آخر.
المشكلة
لديك بيانات مثل theme أو بيانات المستخدم وتريد استخدامها في أكثر من مكون. تمريرها عبر props في كل مستوى يجعل الكود مزعجًا وصعب الصيانة.
الحل
نستخدم createContext و useContext لمشاركة البيانات
مباشرة مع المكونات التي تحتاجها.
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
function toggleTheme() {
setTheme((currentTheme) =>
currentTheme === "light" ? "dark" : "light"
);
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemeButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme}>
Current theme: {theme}
</button>
);
}
function App() {
return (
<ThemeProvider>
<ThemeButton />
</ThemeProvider>
);
}
export default App;
متى تستخدم useContext؟
- عند مشاركة بيانات المستخدم بين عدة مكونات.
- عند إدارة الثيم مثل dark mode و light mode.
- عند مشاركة اللغة أو إعدادات التطبيق.
مقارنة سريعة بين الأنواع
| Hook | الاستخدام | متى تحتاجه؟ |
|---|---|---|
| useState | إدارة حالة بسيطة | عداد، input، toggle |
| useMemo | حفظ نتيجة عملية حسابية | فلترة، ترتيب، حسابات ثقيلة |
| useEffect | تنفيذ كود بعد Render | API، localStorage، events |
| useCallback | حفظ مرجع الدالة | تحسين الأداء مع memo |
| useDeferredValue | تأجيل قيمة ثقيلة | البحث داخل قوائم كبيرة |
| useLayoutEffect | تنفيذ كود قبل الرسم النهائي | قياس العناصر ومنع الوميض |
| useTransition | جعل تحديث معين أقل أولوية | القوائم الثقيلة والتحديثات غير العاجلة |
| useReducer | إدارة حالة معقدة | Actions متعددة ومنطق أكبر |
| useContext | مشاركة الحالة بين المكونات | Theme، user، language |
مثال عملي يجمع أكثر من Hook
في هذا المثال سنستخدم useState مع useMemo و useDeferredValue لبناء بحث سريع داخل قائمة منتجات.
المشكلة
البحث داخل قائمة كبيرة قد يسبب بطء في الكتابة لأن عملية الفلترة تحدث مع كل حرف.
الحل
نخزن النص باستخدام useState، ثم نؤجل قيمة البحث باستخدام useDeferredValue، وبعدها نحفظ نتيجة الفلترة باستخدام useMemo.
import { useDeferredValue, useMemo, useState } from "react";
function ProductSearch() {
const [search, setSearch] = useState("");
const deferredSearch = useDeferredValue(search);
const products = Array.from({ length: 10000 }, (_, index) => ({
id: index + 1,
name: `Product ${index + 1}`,
}));
const filteredProducts = useMemo(() => {
return products.filter((product) =>
product.name.toLowerCase().includes(deferredSearch.toLowerCase())
);
}, [deferredSearch]);
return (
<div>
<h2>Products Search</h2>
<input
value={search}
onChange={(event) => setSearch(event.target.value)}
placeholder="Search product..."
/>
<p>Results: {filteredProducts.length}</p>
{filteredProducts.slice(0, 20).map((product) => (
<p key={product.id}>{product.name}</p>
))}
</div>
);
}
export default ProductSearch;
أخطاء شائعة عند التعامل مع State في React
1. استخدام useMemo في كل مكان
لا تستخدم useMemo إلا عندما تكون العملية مكلفة فعلًا أو تؤثر على الأداء.
استخدامه بدون سبب قد يجعل الكود أعقد بدون فائدة واضحة.
2. نسيان dependency array في useEffect
إذا نسيت dependency array، فقد يعمل useEffect مع كل Render،
وهذا قد يسبب مشاكل في الأداء أو طلبات API متكررة.
// خطأ
useEffect(() => {
fetchData();
});
// أفضل
useEffect(() => {
fetchData();
}, []);
3. استخدام useContext لكل شيء
useContext مفيد لمشاركة البيانات، لكنه ليس بديلًا دائمًا لإدارة
الحالة. إذا كانت الحالة تتغير كثيرًا أو كبيرة جدًا، قد تحتاج إلى تقسيم Context
أو استخدام حل آخر لإدارة الحالة.
الخلاصة
إدارة الحالة في React لا تعتمد على useState فقط.
كل Hook له استخدام مختلف ويحل مشكلة معينة.
استخدم useState للحالات البسيطة، و useReducer للحالات المعقدة، و useContext لمشاركة البيانات، و useMemo و useCallback لتحسين الأداء، و useEffect للتعامل مع التأثيرات الجانبية، أما useDeferredValue و useTransition فهما مفيدان جدًا
عند التعامل مع تحديثات ثقيلة تحتاج إلى تجربة مستخدم أسرع.
Sources
لمزيد من المقالات : كيفية استخدام Hive في Flutter لإدارة البيانات بكفاءة






