React
Context API في React — إدارة الحالة العامة 2026
📅 2026-10-18⏱ 12 دقائق قراءة
في المقالات السابقة، تعلمت إدارة الحالة داخل مكوّنة واحدة. لكن ماذا لو احتجت **مشاركة البيانات** بين مكونات كثيرة؟ هنا يأتي دور **Context API**.
في هذا الدليل العملي، سنأخذك خطوة بخطوة لتعلم Context API وكيفية استخدامه لإدارة الحالة العامة، مع تمارين وحلول.
## المشكلة: Props Drilling
**Props Drilling** هو تمرير Props عبر **طبقات كثيرة** من المكونات.
**مثال:**
```tsx
// App → Layout → Header → UserMenu → Avatar
function App() {
const user = { name: "أحمد" };
return <Layout user={user} />;
}
function Layout({ user }) {
return <Header user={user} />;
}
function Header({ user }) {
return <UserMenu user={user} />;
}
function UserMenu({ user }) {
return <Avatar user={user} />;
}
function Avatar({ user }) {
return <img alt={user.name} />;
}
```
**⚠️ المشاكل:**
- **كود مكرر:** كل مكوّنة تمرر Props.
- **صعب الصيانة:** تغيير بنية البيانات يؤثر على كل الطبقات.
- **مرهق:** مكونات لا تحتاج البيانات، لكنها تمررها.
## الحل: Context API
**Context** يسمح بمشاركة البيانات **مباشرة** مع أي مكوّنة تحتاجها، بدون Props Drilling.
**تشبيه بسيط:** تخيل أن Props هي **تمرير ورقة من يد إلى يد**. Context هي **إعلان على لوحة** يقرأه كل من يحتاج.
## دورة عمل Context
```
1. إنشاء Context (createContext)
2. توفير القيمة (Provider)
3. استخدام القيمة (useContext)
```
## إنشاء Context
### 1. إنشاء Context جديد
**أنشئ `src/contexts/UserContext.tsx`:**
```tsx
import { createContext, useContext, useState, ReactNode } from "react";
interface User {
name: string;
email: string;
}
interface UserContextType {
user: User | null;
login: (user: User) => void;
logout: () => void;
}
const UserContext = createContext<UserContextType | undefined>(undefined);
export function UserProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = (newUser: User) => setUser(newUser);
const logout = () => setUser(null);
return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
}
export function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error("useUser must be used within UserProvider");
}
return context;
}
```
**شرح:**
- **`createContext`:** يُنشئ Context.
- **`UserProvider`:** يُوفّر القيمة للمكونات.
- **`useUser`:** Hook مخصص للاستخدام.
### 2. تغليف التطبيق
**في `src/main.tsx`:**
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { UserProvider } from "./contexts/UserContext";
import App from "./App";
import "./index.css";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<UserProvider>
<App />
</UserProvider>
</BrowserRouter>
</StrictMode>
);
```
### 3. استخدام Context
```tsx
import { useUser } from "./contexts/UserContext";
function UserProfile() {
const { user, login, logout } = useUser();
if (!user) {
return (
<button onClick={() => login({ name: "أحمد", email: "[email protected]" })}>
تسجيل الدخول
</button>
);
}
return (
<div>
<p>مرحباً {user.name}</p>
<button onClick={logout}>تسجيل الخروج</button>
</div>
);
}
```
**🎉 مبروك! لا مزيد من Props Drilling!**
## أمثلة عملية
### 1. Context للوضع الداكن
**`src/contexts/ThemeContext.tsx`:**
```tsx
import { createContext, useContext, useState, ReactNode } from "react";
type Theme = "light" | "dark";
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within ThemeProvider");
}
return context;
}
```
**الاستخدام:**
```tsx
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
{theme === "light" ? "🌙 داكن" : "☀️ فاتح"}
</button>
);
}
```
### 2. Context للسلة (Cart)
**`src/contexts/CartContext.tsx`:**
```tsx
import { createContext, useContext, useState, ReactNode } from "react";
interface Product {
id: number;
name: string;
price: number;
}
interface CartContextType {
items: Product[];
addItem: (product: Product) => void;
removeItem: (id: number) => void;
clearCart: () => void;
total: number;
}
const CartContext = createContext<CartContextType | undefined>(undefined);
export function CartProvider({ children }: { children: ReactNode }) {
const [items, setItems] = useState<Product[]>([]);
const addItem = (product: Product) => {
setItems((prev) => [...prev, product]);
};
const removeItem = (id: number) => {
setItems((prev) => prev.filter((item) => item.id !== id));
};
const clearCart = () => setItems([]);
const total = items.reduce((sum, item) => sum + item.price, 0);
return (
<CartContext.Provider
value={{ items, addItem, removeItem, clearCart, total }}
>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error("useCart must be used within CartProvider");
}
return context;
}
```
**الاستخدام:**
```tsx
function ProductCard({ product }: { product: Product }) {
const { addItem } = useCart();
return (
<div>
<h3>{product.name}</h3>
<p>{product.price} $</p>
<button onClick={() => addItem(product)}>أضف للسلة</button>
</div>
);
}
function CartSummary() {
const { items, total, removeItem, clearCart } = useCart();
if (items.length === 0) return <p>السلة فارغة</p>;
return (
<div>
<h3>السلة ({items.length})</h3>
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name} - {item.price} $
<button onClick={() => removeItem(item.id)}>❌</button>
</li>
))}
</ul>
<p>الإجمالي: {total} $</p>
<button onClick={clearCart}>إفراغ السلة</button>
</div>
);
}
```
### 3. Context للغة
**`src/contexts/LanguageContext.tsx`:**
```tsx
import { createContext, useContext, useState, ReactNode } from "react";
type Language = "ar" | "en";
const translations = {
ar: {
greeting: "مرحباً",
goodbye: "إلى اللقاء",
},
en: {
greeting: "Hello",
goodbye: "Goodbye",
},
};
interface LanguageContextType {
language: Language;
toggleLanguage: () => void;
t: (key: keyof typeof translations.ar) => string;
}
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
export function LanguageProvider({ children }: { children: ReactNode }) {
const [language, setLanguage] = useState<Language>("ar");
const toggleLanguage = () => {
setLanguage((prev) => (prev === "ar" ? "en" : "ar"));
};
const t = (key: keyof typeof translations.ar) => translations[language][key];
return (
<LanguageContext.Provider value={{ language, toggleLanguage, t }}>
{children}
</LanguageContext.Provider>
);
}
export function useLanguage() {
const context = useContext(LanguageContext);
if (!context) {
throw new Error("useLanguage must be used within LanguageProvider");
}
return context;
}
```
## Context مع TypeScript
### 1. النوع الكامل
```tsx
interface MyContextType {
value: string;
setValue: (v: string) => void;
}
const MyContext = createContext<MyContextType | undefined>(undefined);
```
### 2. التحقق من الوجود
```tsx
export function useMyContext() {
const context = useContext(MyContext);
if (!context) {
throw new Error("useMyContext must be used within MyProvider");
}
return context;
}
```
**الفائدة:** إذا استخدمت `useMyContext` خارج Provider، ستحصل على خطأ واضح.
## تحسين الأداء: فصل Contexts
**⚠️ مشكلة:** تحديث قيمة Context يُعيد رسم **كل** المكونات المستهلكة.
**الحل:** افصل Contexts حسب الاستخدام.
```tsx
// بدلاً من:
const AppContext = createContext({ user, theme, cart });
// استخدم:
const UserContext = createContext(user);
const ThemeContext = createContext(theme);
const CartContext = createContext(cart);
```
**الفائدة:** كل مكوّنة تُعيد الرسم فقط عند تغيّر الـ Context الذي يستخدمه.
## useReducer مع Context
للحالات المعقدة، استخدم `useReducer`:
```tsx
import { createContext, useContext, useReducer, ReactNode } from "react";
type State = { count: number };
type Action = { type: "increment" } | { type: "decrement" } | { type: "reset" };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
}
}
const CountContext = createContext<{
state: State;
dispatch: React.Dispatch<Action>;
} | undefined>(undefined);
export function CountProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<CountContext.Provider value={{ state, dispatch }}>
{children}
</CountContext.Provider>
);
}
export function useCount() {
const context = useContext(CountContext);
if (!context) throw new Error("useCount must be used within CountProvider");
return context;
}
```
**الاستخدام:**
```tsx
function Counter() {
const { state, dispatch } = useCount();
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "reset" })}>إعادة</button>
</div>
);
}
```
## Context vs Redux
<table>
<thead>
<tr>
<th>المعيار</th>
<th>Context API</th>
<th>Redux</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>التعقيد</strong></td>
<td>🟢 بسيط</td>
<td>🟡 متوسط</td>
</tr>
<tr>
<td><strong>الحجم</strong></td>
<td>🪶 لا مكتبة</td>
<td>📦 مكتبة خارجية</td>
</tr>
<tr>
<td><strong>الأداء</strong></td>
<td>✅ جيد</td>
<td>✅ ممتاز</td>
</tr>
<tr>
<td><strong>الأدوات</strong></td>
<td>❌ محدودة</td>
<td>✅ DevTools</td>
</tr>
<tr>
<td><strong>الاستخدام</strong></td>
<td>تطبيقات صغيرة/متوسطة</td>
<td>تطبيقات كبيرة</td>
</tr>
</tbody>
</table>
**القاعدة:**
- **Context:** للبيانات البسيطة (User, Theme, Language).
- **Redux:** للحالة المعقدة (تطبيقات كبيرة).
## أفضل الممارسات
### 1. Context مخصص لكل نوع بيانات
```tsx
// ✅ جيد:
<UserProvider>
<ThemeProvider>
<LanguageProvider>
<App />
</LanguageProvider>
</ThemeProvider>
</UserProvider>
```
### 2. Hook مخصص لكل Context
```tsx
export function useUser() {
const context = useContext(UserContext);
if (!context) throw new Error("useUser must be used within UserProvider");
return context;
}
```
### 3. تجنب Context للحالات المحلية
**لا تستخدم Context** لحالة تستخدمها مكوّنة واحدة. استخدم `useState`.
## تمارين عملية
### تمرين 1: Context بسيط
أنشئ Context لتخزين اسم المستخدم.
**الحل:**
```tsx
const NameContext = createContext<{ name: string; setName: (n: string) => void } | undefined>(undefined);
export function NameProvider({ children }: { children: ReactNode }) {
const [name, setName] = useState("");
return <NameContext.Provider value={{ name, setName }}>{children}</NameContext.Provider>;
}
export function useName() {
const ctx = useContext(NameContext);
if (!ctx) throw new Error("useName must be used within NameProvider");
return ctx;
}
```
### تمرين 2: Context للوضع الداكن
أنشئ Context لتبديل الوضع الداكن.
**الحل:**
```tsx
type Theme = "light" | "dark";
const ThemeContext = createContext<{ theme: Theme; toggle: () => void } | undefined>(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
return <ThemeContext.Provider value={{ theme, toggle }}>{children}</ThemeContext.Provider>;
}
```
### تمرين 3: Context للعداد
أنشئ Context لعداد مشترك بين مكونات.
**الحل:**
```tsx
const CountContext = createContext<{ count: number; increment: () => void } | undefined>(undefined);
export function CountProvider({ children }: { children: ReactNode }) {
const [count, setCount] = useState(0);
const increment = () => setCount((c) => c + 1);
return <CountContext.Provider value={{ count, increment }}>{children}</CountContext.Provider>;
}
```
### تمرين 4: Context للسلة
أنشئ Context لإدارة سلة التسوق.
**الحل:**
```tsx
interface Product { id: number; name: string; price: number; }
interface CartContextType {
items: Product[];
add: (p: Product) => void;
remove: (id: number) => void;
total: number;
}
const CartContext = createContext<CartContextType | undefined>(undefined);
export function CartProvider({ children }: { children: ReactNode }) {
const [items, setItems] = useState<Product[]>([]);
const add = (p: Product) => setItems((prev) => [...prev, p]);
const remove = (id: number) => setItems((prev) => prev.filter((i) => i.id !== id));
const total = items.reduce((sum, i) => sum + i.price, 0);
return (
<CartContext.Provider value={{ items, add, remove, total }}>
{children}
</CartContext.Provider>
);
}
```
### تمرين 5: Context للغة
أنشئ Context للتبديل بين العربية والإنجليزية.
**الحل:**
```tsx
type Language = "ar" | "en";
const LanguageContext = createContext<{ language: Language; toggle: () => void } | undefined>(undefined);
export function LanguageProvider({ children }: { children: ReactNode }) {
const [language, setLanguage] = useState<Language>("ar");
const toggle = () => setLanguage((l) => (l === "ar" ? "en" : "ar"));
return <LanguageContext.Provider value={{ language, toggle }}>{children}</LanguageContext.Provider>;
}
```
### تمرين 6: Context للإشعارات
أنشئ Context لعرض الإشعارات.
**الحل:**
```tsx
interface Notification { id: number; message: string; }
interface NotifContextType {
notifications: Notification[];
addNotification: (msg: string) => void;
}
const NotifContext = createContext<NotifContextType | undefined>(undefined);
export function NotifProvider({ children }: { children: ReactNode }) {
const [notifications, setNotifications] = useState<Notification[]>([]);
const addNotification = (message: string) => {
const id = Date.now();
setNotifications((prev) => [...prev, { id, message }]);
setTimeout(() => {
setNotifications((prev) => prev.filter((n) => n.id !== id));
}, 3000);
};
return (
<NotifContext.Provider value={{ notifications, addNotification }}>
{children}
<div className="notifications">
{notifications.map((n) => <div key={n.id}>{n.message}</div>)}
</div>
</NotifContext.Provider>
);
}
```
### تمرين 7: Context للمصادقة
أنشئ Context لتسجيل الدخول والخروج.
**الحل:**
```tsx
interface User { name: string; email: string; }
interface AuthContextType {
user: User | null;
login: (user: User) => void;
logout: () => void;
isAuthenticated: boolean;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = (u: User) => setUser(u);
const logout = () => setUser(null);
const isAuthenticated = !!user;
return (
<AuthContext.Provider value={{ user, login, logout, isAuthenticated }}>
{children}
</AuthContext.Provider>
);
}
```
### تمرين 8: Context مع useReducer
أنشئ Context لإدارة حالة معقدة بـ useReducer.
**الحل:**
```tsx
type State = { todos: { id: number; text: string }[] };
type Action =
| { type: "add"; text: string }
| { type: "remove"; id: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "add":
return { todos: [...state.todos, { id: Date.now(), text: action.text }] };
case "remove":
return { todos: state.todos.filter((t) => t.id !== action.id) };
}
}
const TodoContext = createContext<{ state: State; dispatch: React.Dispatch<Action> } | undefined>(undefined);
export function TodoProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, { todos: [] });
return <TodoContext.Provider value={{ state, dispatch }}>{children}</TodoContext.Provider>;
}
```
## حل المشاكل الشائعة
### 🔴 المشكلة 1: `useContext must be used within Provider`
**السبب:** استخدمت `useContext` خارج Provider.
**الحل:** تأكد من تغليف التطبيق بـ `<Provider>`.
### 🔴 المشكلة 2: Context يُعيد الرسم كثيراً
**السبب:** قيمة Context تتغير كثيراً.
**الحل:** استخدم `useMemo` للقيمة:
```tsx
const value = useMemo(() => ({ user, login, logout }), [user]);
```
### 🔴 المشكلة 3: `Cannot read property 'xxx' of undefined`
**السبب:** لم تُغلّف المكوّنة بـ Provider.
**الحل:** ارفع الـ Provider لمستوى أعلى.
### 🔴 المشكلة 4: Context لا يتحدّث
**السبب:** تعديل مباشر بدلاً من `setState`.
**الحل:** استخدم `setState` الصحيح.
## جدول Context API
<table>
<thead>
<tr>
<th>الدالة</th>
<th>الوظيفة</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>createContext</code></td>
<td>إنشاء Context</td>
</tr>
<tr>
<td><code>Provider</code></td>
<td>توفير القيمة</td>
</tr>
<tr>
<td><code>useContext</code></td>
<td>استخدام القيمة</td>
</tr>
<tr>
<td><code>useReducer</code></td>
<td>إدارة حالة معقدة</td>
</tr>
</tbody>
</table>
## قائمة تحقق نهائية
<table>
<thead>
<tr>
<th>المهمة</th>
<th>الحالة</th>
</tr>
</thead>
<tbody>
<tr>
<td>فهم Props Drilling</td>
<td>⬜</td>
</tr>
<tr>
<td>إنشاء Context</td>
<td>⬜</td>
</tr>
<tr>
<td>استخدام Provider</td>
<td>⬜</td>
</tr>
<tr>
<td>استخدام useContext</td>
<td>⬜</td>
</tr>
<tr>
<td>Context مع TypeScript</td>
<td>⬜</td>
</tr>
<tr>
<td>useReducer مع Context</td>
<td>⬜</td>
</tr>
<tr>
<td>حل التمارين الثمانية</td>
<td>⬜</td>
</tr>
</tbody>
</table>
## ماذا بعد هذا المقال؟
الآن بعد أن أتقنت Context API، أنت جاهز للمقال الأخير:
1. **مشروع متكامل** — تطبيق كامل يجمع كل ما تعلمته.
## الخلاصة
في هذا المقال، تعلمت:
- ✅ مشكلة Props Drilling.
- ✅ إنشاء Context.
- ✅ Provider و useContext.
- ✅ Context مع TypeScript.
- ✅ استخدام useReducer مع Context.
- ✅ فصل Contexts للأداء.
- ✅ Context vs Redux.
- ✅ حل 8 تمارين عملية.
**تذكر:** Context API أداة قوية، لكن **لا تبالغ في استخدامه**. أحياناً Props أبسط وأوضح.
**هل أتقنت Context API؟ شاركنا في التعليقات!**