React📅 2026-10-18⏱ 12 دقائق قراءة⚛️ مقال 9 من 10

Context API في React — إدارة الحالة العامة 2026

دليل عملي مفصل لـ Context API في React — إنشاء Context، Provider، useContext، مع تمارين وحلول

في المقالات السابقة، تعلمت إدارة الحالة داخل مكوّنة واحدة. لكن ماذا لو احتجت مشاركة البيانات بين مكونات كثيرة؟ هنا يأتي دور Context API.

في هذا الدليل العملي، سنأخذك خطوة بخطوة لتعلم Context API وكيفية استخدامه لإدارة الحالة العامة، مع تمارين وحلول.

المشكلة: Props Drilling

Props Drilling هو تمرير Props عبر طبقات كثيرة من المكونات.

مثال:

// 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:

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:

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

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:

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;
}

الاستخدام:

function ThemeToggle() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button onClick={toggleTheme}>
      {theme === "light" ? "🌙 داكن" : "☀️ فاتح"}
    </button>
  );
}

2. Context للسلة (Cart)

src/contexts/CartContext.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;
}

الاستخدام:

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:

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. النوع الكامل

interface MyContextType {
  value: string;
  setValue: (v: string) => void;
}

const MyContext = createContext<MyContextType | undefined>(undefined);

2. التحقق من الوجود

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 حسب الاستخدام.

// بدلاً من:
const AppContext = createContext({ user, theme, cart });

// استخدم:
const UserContext = createContext(user);
const ThemeContext = createContext(theme);
const CartContext = createContext(cart);

الفائدة: كل مكوّنة تُعيد الرسم فقط عند تغيّر الـ Context الذي يستخدمه.

useReducer مع Context

للحالات المعقدة، استخدم useReducer:

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;
}

الاستخدام:

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

المعيارContext APIRedux
التعقيد🟢 بسيط🟡 متوسط
الحجم🪶 لا مكتبة📦 مكتبة خارجية
الأداء✅ جيد✅ ممتاز
الأدوات❌ محدودة✅ DevTools
الاستخدامتطبيقات صغيرة/متوسطةتطبيقات كبيرة

القاعدة:

  • Context: للبيانات البسيطة (User, Theme, Language).
  • Redux: للحالة المعقدة (تطبيقات كبيرة).

أفضل الممارسات

1. Context مخصص لكل نوع بيانات

// ✅ جيد:
<UserProvider>
  <ThemeProvider>
    <LanguageProvider>
      <App />
    </LanguageProvider>
  </ThemeProvider>
</UserProvider>

2. Hook مخصص لكل Context

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 لتخزين اسم المستخدم.

الحل:

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 لتبديل الوضع الداكن.

الحل:

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 لعداد مشترك بين مكونات.

الحل:

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 لإدارة سلة التسوق.

الحل:

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 للتبديل بين العربية والإنجليزية.

الحل:

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 لعرض الإشعارات.

الحل:

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 لتسجيل الدخول والخروج.

الحل:

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.

الحل:

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 للقيمة:

const value = useMemo(() => ({ user, login, logout }), [user]);

🔴 المشكلة 3: Cannot read property 'xxx' of undefined

السبب: لم تُغلّف المكوّنة بـ Provider.

الحل: ارفع الـ Provider لمستوى أعلى.

🔴 المشكلة 4: Context لا يتحدّث

السبب: تعديل مباشر بدلاً من setState.

الحل: استخدم setState الصحيح.

جدول Context API

الدالةالوظيفة
createContextإنشاء Context
Providerتوفير القيمة
useContextاستخدام القيمة
useReducerإدارة حالة معقدة

قائمة تحقق نهائية

المهمةالحالة
فهم Props Drilling⬜
إنشاء Context⬜
استخدام Provider⬜
استخدام useContext⬜
Context مع TypeScript⬜
useReducer مع Context⬜
حل التمارين الثمانية⬜

ماذا بعد هذا المقال؟

الآن بعد أن أتقنت Context API، أنت جاهز للمقال الأخير:

  1. مشروع متكامل — تطبيق كامل يجمع كل ما تعلمته.

الخلاصة

في هذا المقال، تعلمت:

  • ✅ مشكلة Props Drilling.
  • ✅ إنشاء Context.
  • ✅ Provider و useContext.
  • ✅ Context مع TypeScript.
  • ✅ استخدام useReducer مع Context.
  • ✅ فصل Contexts للأداء.
  • ✅ Context vs Redux.
  • ✅ حل 8 تمارين عملية.

تذكر: Context API أداة قوية، لكن لا تبالغ في استخدامه. أحياناً Props أبسط وأوضح.

هل أتقنت Context API؟ شاركنا في التعليقات!

📚 مقالات ذات صلة

React

مشروع React متكامل — تطبيق متجر إلكتروني 2026

مشروع عملي شامل لبناء تطبيق متجر إلكتروني بـ React و TypeScript — مع السلة، البحث، التصفية، والحفظ التلقائي

React

React Router — التنقل بين الصفحات في React 2026

دليل عملي مفصل لـ React Router — التنقل، المسارات، الروابط، والمعاملات، مع تمارين وحلول

React

القوائم والشروط في React — دليل شامل 2026

دليل عملي مفصل للقوائم والشروط في React — map, filter, key، والتصيير الشرطي، مع تمارين وحلول