15 أغسطس 2026•Yousef Dawood

ايه هوا use ف React وازاي بتشتغل مع ال Context و Promises

شرح تفصيلي لـ use() في React 19، إزاي بتشتغل مع Context والـ Promises، وإيه سبب مشكلة Infinite Fetch Requests وإزاي نحلها.

جدول المحتويات

use() في React 19: إيه حكايته وبيشتغل إزاي من جوّه؟

use() دي API جديدة في React 19 بتخلينا نقرأ قيمة من Context أو نتعامل مع Promise مباشرة من جوه الـ Component.

قبل ما تبدأ

لازم يكون عندك خلفية عن Context API و Promise قبل ما تكمل قراءة المقالة.

ملحوظة مهمة

رغم اسمها، use() مش Hook، دي API عادية من React زي createPortal و cloneElement، فده معناه إنها ممكن تتنادى جوه Loops و Conditional Statements زي if من غير أي مشاكل. الكلام ده موضح في موقع React الرسمي.


هيكل المشروع

الأكواد اللي هنشوفها في المقالة دي متلمة من مشروع React + TypeScript بسيط بالشكل ده

product.tsx
auth-provider.tsx
App.tsx
main.tsx
utils.ts
index.html
package.json
pnpm-lock.yaml
tsconfig.json

use() مع Context API

خلينا نبدأ بأول استخدام: إزاي use() بتشتغل مع الـ Context API.

الطريقة المعتادة useContext

الكود اللي احنا متعودين عليه بيبقى بالشكل ده

providers/auth-provider.tsx
import { createContext, useContext, useState } from "react";

export const AuthContext = createContext<
  | {
      token: string | null;
      setToken: (token: string) => void;
    }
  | undefined
>(undefined);

type AuthProviderProps = {
  readonly children: React.ReactNode;
};

export function AuthProvider({ children }: AuthProviderProps) {
  const [token, setToken] = useState<string | null>(null);

  return (
    <AuthContext.Provider value={{ token, setToken }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = function () {
  const data = useContext(AuthContext);
  if (data === undefined) throw new Error("Context used out of its provider");
  return data;
};

الكود ده أكيد شفتوه كتير قبل كده، بس في React 19 بقى عندنا تغييرين مهمين عليه.

التغييرات الجديدة في React 19

التغيير الأول: الـ Context نفسه بقى Provider

بدل ما نكتب <AuthContext.Provider value={...}> بقينا نقدر نكتب <AuthContext value={...}> مباشرة. الـ .Provider لسه موجودة ومش اتشالت، بس بقى عندنا طريقة أبسط.

providers/auth-provider.tsx
return (
  <AuthContext.Provider value={{ token, setToken }}>
    {children}
  </AuthContext.Provider>
);

ملحوظة عن الـ JSX

إحنا عارفين إن React مش بتقبل أسماء Component تبدأ بحرف صغير جوه الـ JSX، بس لما بنعمل Access لـ Property جوه Variable هوا في الأصل Component، زي authContext.Provider، مفيش أي مشكلة وده بيشتغل عادي.

التغيير الثاني: use() بدل useContext

بقينا نقدر نستخدم use() بدل useContext، ونفس الاستعمال بالظبط

providers/auth-provider.tsx
import { use } from 'react';

export const useAuth = function () {
  const data = use(AuthContext); 
  if (data === undefined) throw new Error('Context used out of its provider');
  return data;
};

طب ليه نستخدم use() بدل useContext أصلاً؟

بما إن use() مش Hook، تقدر تستخدمها جوه Conditional Statements و Loops من غير ما تكسر Rules of Hooks

router.tsx
import { use } from "react";

export function Router({ isProtected }: { isProtected: boolean }) {
  if (!isProtected) return <Page />;

  const { token } = use(AuthContext); 
  if (!token) return redirect(<Login />);

  return <Page />;
}

الكود ده عادي جدًا ومش هيرمي أي Error، وده برضو موضح في React Documentation.


use() مع Promise

الاستخدام التاني، وهو الأهم، هو إزاي بنستخدم use() مع الـ Promise مباشرة.

الطريقة التقليدية بـ useEffect

في الغالب إحنا بنستخدم الكود ده لجلب البيانات

App.tsx
import { useState, useEffect } from "react";

export default function App() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchPosts = async () => {
      setLoading(true);
      const response = await fetch("https://dummyjson.com/products");
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      setLoading(false);
      setPosts(data);
    };

    fetchPosts();
  }, []);

  if (loading) return <p>Loading data...</p>;

  return <pre>{JSON.stringify(posts, null, 2)}</pre>;
}

وبغض النظر عن مشاكل الكود ده زي مفيش Deduplication وممكن يحصل Race Condition

الطريقة الجديدة باستخدام use()

بدل الكود ده كله، ممكن نعمل كده

components/product.tsx
import { use } from "react";

const fetchProductById = async function (
  id: number,
): Promise<{ title: string }> {
  const res = await fetch(`https://dummyjson.com/products/${id}`);
  if (!res.ok) throw new Error("Something went wrong");
  const data = await res.json();
  return data;
};

type Product = {
  id: number;
};

export default function Product({ id }: Product) {
  const product = use(fetchProductById(id)); 
  return <h1>{product.title}</h1>;
}
App.tsx
import Product from "./components/product";

export default function App() {
  return <Product id={1} />;
}

بكل بساطة use(fetchProductById(id)) بس كده بيقرا نتيجة الـ Promise: لو لسه pending، React بتعمل suspend للـ Component، ولو resolved بترجع القيمة، ولو rejected بيتدفع مباشرة لأقرب ErrorBoundary.

مشكلة الـ Infinite Fetch Requests

في مشكلة كبيرة جدًا وناس كتير بتغفل عنها.

مشكلة شائعة

لما React تشوف الـ Promise الراجع من fetchProductById لسه pending، use() هتحاول تجيب منه الـ Data فـ React هتعمل re-render للـ Component. ولما نعمل Call تاني لـ fetchProductById هيرجع Reference Promise مختلف عن الأول، فـ React هتعمل re-render تاني... وكده لغاية ما نلاقي نفسنا في حلقة لا نهائية من الطلبات.

صوره توضح Infinite fetches requests

الحل: عمل Cache للـ Promises

الفكرة إننا نعمل Cache لل Promises اللي جايه من نفس الـ id: لو الـ id موجود في الـ Cache نرجع نفس الـ Promise بتاعه، ولو مش موجود نعمل Promise جديد ونخزنه

utils.ts
export const cachePromises = function <T extends Promise<unknown>>() {
  const promiseReferences = new Map<number, T>();

  return <TPromise extends T>(
    id: number,
    callback: (id: number) => TPromise,
  ) => {
    const promise = (promiseReferences.get(id) ?? callback(id)) as TPromise;
    promiseReferences.set(id, promise);
    return promise;
  };
};

ملحوظة عن الـ TypeScript

جزء الـ TypeScript هنا معتمد على الـ Generics، فلو مش واضح ليك، اسالني وانا هساعدك فيها

والكود كامل بعد التعديل بيبقى بالشكل ده

components/product.tsx
import { use } from "react";
import { cachePromises } from "../utils";

const fetchProductById = async function (
  id: number,
): Promise<{ title: string }> {
  const res = await fetch(`https://dummyjson.com/products/${id}`);
  if (!res.ok) throw new Error("Something went wrong");
  const data = await res.json();
  return data;
};

type Product = {
  id: number;
};

const productCache = cachePromises(); 

export default function Product({ id }: Product) {
  const product = use(productCache(id, fetchProductById)); 
  return <h1>{product.title}</h1>;
}

وهنكون حلينا مشكله ال Infinite Fetch Requests

Single Request

تنبيه

هتلاحظ إن زرار الـ Refresh بيفضل معلق، وده لأن أي Component بيستخدم use() مع Promise لازم يتحوط بـ Suspense عشان الـ Fallback يظهر لحد ما الـ Data توصل. السبب بالتفصيل في القسم الجاي.

use() behavior without suspense


إزاي use() و Suspense بيشتغلوا مع بعض؟

عشان الـ Fallback بتاع Suspense يشتغل، لازم الكود اللي تحته يعمل throw لـ Promise. أول ما Suspense تشوف Promise هتحط الـ Fallback لحد ما نفس الـ Promise (نفس Reference) يبقى Resolved.

App.tsx
import { Suspense } from "react";
import Product from "./components/product";

export default function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <Product id={1} />
    </Suspense>
  );
}

وبكدا هيكون في Loading State لحد مايبقي Resolved Promise

use() with Suspense

معلومه

use() بتتشيك الأول هل اللي اتبعت ليها Context ولا Promise، عشان كل واحد فيهم له Mechanism مختلف لجلب الـ Data.

use() source code

تقليد مبسط لـ use()

لو حبينا نعمل إحنا الجزء اللي بيستخدم الـ Promise، هيبقى بسيط جدًا

use.ts
type UsePromise<T> = {
  data?: T;
  status?: 'pending' | 'rejected' | 'resolved';
  reason?: string;
} & Promise<T>;

export function use<T>(promise: UsePromise<T>): T {
  const usePromise = promise;

  // prettier-ignore
  if (usePromise.status === "pending") 
    throw usePromise; // This will trigger a <Suspense /> Boundary

  // prettier-ignore
  if (usePromise.status === "rejected") 
    throw usePromise.reason; // This will trigger a <ErrorBoundary /> Boundary

  // prettier-ignore
  if (usePromise.status === "resolved")
    return usePromise.data!; // This Wouldn't Be Returned until it's Resolved

  usePromise.status = 'pending';

  usePromise.then(
    value => {
      usePromise.status = 'resolved';
      usePromise.data = value;
    },
    error => {
      usePromise.status = 'rejected';
      usePromise.reason = error;
    },
  );

  throw usePromise;
}

ودا ال Repo

ملحوظة

ده Implementation بسيط عشان نفهم الفكرة بس، كود React الأصلي أعقد بكتير و Optimized زيادة، والكود ده للتعليم فقط مش لل Production.

التعامل مع الأخطاء عبر ErrorBoundary

لاحظ إن الـ ErrorBoundary هي اللي بتمسك أي Promise اتعمله Reject، فهتلاقينا دايمًا بنستخدم ErrorBoundary حوالين Suspense بالشكل ده

App.tsx
export default function App() {
  return (
    <ErrorBoundary fallback={<div>Something went wrong</div>}>
      <Suspense fallback={<p>Loading...</p>}>
        <Product id={1} />
      </Suspense>
    </ErrorBoundary>
  );
}

الخلاصة

وبكده نكون فككنا use() من أول استخدامها مع Context والـ Promise، لحد ما وصلنا لفكرة Suspense و ErrorBoundary والـ Internals اللي بتحرك كل ده من جوه. ودا لينك ال Repo اللي احنا شغالين عليه

لو لقيت أي نقطة محتاجة تصحيح أو عندك إضافة، شاركني بيها ❤️ ولا تنسوني من دعائكم.