ايه هوا 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 بسيط بالشكل ده
use() مع Context API
خلينا نبدأ بأول استخدام: إزاي use() بتشتغل مع الـ Context API.
الطريقة المعتادة useContext
الكود اللي احنا متعودين عليه بيبقى بالشكل ده
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 لسه موجودة ومش اتشالت، بس بقى عندنا طريقة أبسط.
return (
<AuthContext.Provider value={{ token, setToken }}>
{children}
</AuthContext.Provider>
);ملحوظة عن الـ JSX
إحنا عارفين إن React مش بتقبل أسماء Component تبدأ بحرف صغير جوه الـ JSX، بس لما بنعمل Access لـ Property جوه Variable هوا في الأصل Component، زي authContext.Provider، مفيش أي مشكلة وده بيشتغل عادي.
التغيير الثاني: use() بدل useContext
بقينا نقدر نستخدم use() بدل useContext، ونفس الاستعمال بالظبط
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
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
في الغالب إحنا بنستخدم الكود ده لجلب البيانات
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()
بدل الكود ده كله، ممكن نعمل كده
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>;
}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 تاني... وكده لغاية ما نلاقي نفسنا في حلقة لا نهائية من الطلبات.

الحل: عمل Cache للـ Promises
الفكرة إننا نعمل Cache لل Promises اللي جايه من نفس الـ id: لو الـ id موجود في الـ Cache نرجع نفس الـ Promise بتاعه، ولو مش موجود نعمل Promise جديد ونخزنه
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، فلو مش واضح ليك، اسالني وانا هساعدك فيها
والكود كامل بعد التعديل بيبقى بالشكل ده
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

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

إزاي use() و Suspense بيشتغلوا مع بعض؟
عشان الـ Fallback بتاع Suspense يشتغل، لازم الكود اللي تحته يعمل throw لـ Promise. أول ما Suspense تشوف Promise هتحط الـ Fallback لحد ما نفس الـ Promise (نفس Reference) يبقى Resolved.
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() بتتشيك الأول هل اللي اتبعت ليها Context ولا Promise، عشان كل واحد فيهم له Mechanism مختلف لجلب الـ Data.

تقليد مبسط لـ use()
لو حبينا نعمل إحنا الجزء اللي بيستخدم الـ Promise، هيبقى بسيط جدًا
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 بالشكل ده
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 اللي احنا شغالين عليه
لو لقيت أي نقطة محتاجة تصحيح أو عندك إضافة، شاركني بيها ❤️ ولا تنسوني من دعائكم.