شرح React Query (TanStack Query) من الصفر بالعربي

هذا الدرس هو شرح React Query (TanStack Query) من الصفر بالعربي على شكل React Query tutorial بالعربي. عندما تبدأ بجلب البيانات في React من API، ستواجه نفس التحديات دائمًا: تحميل، أخطاء، كاش، إعادة جلب. هنا يأتي دور React Query (المعروف الآن باسم TanStack Query) ليحل هذه المشاكل بشكل احترافي.

لماذا React Query لإدارة البيانات في React؟

  • كاش ذكي للبيانات بدل تكرار نفس الطلب.
  • حالات تحميل وأخطاء جاهزة بدون تعقيد.
  • إعادة جلب تلقائية عند التركيز أو تغيير الشبكة.
  • تحكم كامل في التحديث وإعادة المحاولة.

التثبيت والإعداد الأساسي لـ TanStack Query

npm install @tanstack/react-query
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

شرح مفصل خطوة بخطوة للمبتدئ جدًا:

  • استوردنا QueryClient و QueryClientProvider.
  • أنشأنا queryClient مرة واحدة فقط.
  • لفّينا التطبيق بـ QueryClientProvider حتى تصل React Query لكل المكوّنات.
  • بعد ذلك تستطيع استخدام useQuery في أي مكان داخل التطبيق.

كأنك وضعت “مركز إدارة بيانات” واحد للتطبيق كله، بدل ما كل مكوّن يحاول يدير البيانات لوحده.

مثال عملي: useQuery لجلب البيانات في React

import { useQuery } from "@tanstack/react-query";

async function fetchUsers() {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  if (!res.ok) throw new Error("فشل تحميل البيانات");
  return res.json();
}

function Users() {
  const { data, isLoading, error } = useQuery({
    queryKey: ["users"],
    queryFn: fetchUsers,
  });

  if (isLoading) return <p>جاري التحميل...</p>;
  if (error) return <p className="text-danger">{error.message}</p>;

  return (
    <ul>
      {data.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

الشرح خطوة بخطوة بالتفصيل:

  • أنشأنا دالة fetchUsers وظيفتها الوحيدة جلب البيانات من الـ API.
  • داخل الدالة: نتحقق من res.ok، إذا فشل الطلب نرمي خطأ.
  • إذا نجح الطلب نحول الرد إلى JSON ثم نعيده.
  • في المكوّن Users استدعينا useQuery بدل استخدام useEffect و useState يدويًا.
  • أعطينا الاستعلام اسمًا واضحًا عبر queryKey: ["users"] لكي يدير الكاش بشكل صحيح.
  • القيمة queryFn هي الدالة التي تجلب البيانات فعليًا.
  • عندما يبدأ أول تحميل، isLoading يصبح true ونُظهر رسالة التحميل.
  • إذا حدث خطأ يظهر داخل error ونُظهر رسالة مناسبة.
  • إذا نجح الطلب، تظهر البيانات في data ونعرض القائمة.

لاحظ كيف اختفت معظم حالات إدارة الـ state يدويًا. React Query تتولى الكاش (React Query cache)، والتحكم في التحميل والأخطاء بشكل مباشر.

إعدادات مهمة: queryKey و staleTime و refetchOnWindowFocus

  • staleTime: مدة اعتبار البيانات جديدة قبل إعادة الجلب.
  • refetchOnWindowFocus: إعادة الجلب عند العودة للصفحة.
  • enabled: تشغيل أو إيقاف الاستعلام حسب شرط معيّن.
const { data } = useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
  staleTime: 1000 * 60, // دقيقة
  refetchOnWindowFocus: false,
});

تفسير مفصل خطوة بخطوة:

  • queryKey: هو الاسم الفريد للاستعلام، ومنه React Query تعرف أي كاش تستخدم.
  • queryFn: الدالة المسؤولة عن الجلب الفعلي للبيانات.
  • staleTime: المدة التي تعتبر فيها البيانات جديدة، هنا دقيقة واحدة.
  • refetchOnWindowFocus: عند العودة للتبويب لن يعيد الجلب تلقائيًا.

هذا يجعل إدارة البيانات في React أكثر هدوءًا وأقل طلبات غير ضرورية.

استخدام useMutation لإرسال البيانات (POST/PUT/DELETE)

import { useMutation, useQueryClient } from "@tanstack/react-query";

function AddUser() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: async (newUser) => {
      const res = await fetch("https://jsonplaceholder.typicode.com/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(newUser),
      });
      if (!res.ok) throw new Error("فشل إضافة المستخدم");
      return res.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
  });

  return (
    <button onClick={() => mutation.mutate({ name: "Ali" })}>
      إضافة مستخدم
    </button>
  );
}

شرح مفصل خطوة بخطوة:

  • useMutation مخصص للعمليات التي تغيّر البيانات (POST/PUT/DELETE).
  • داخل mutationFn نرسل المستخدم الجديد إلى الـ API.
  • إذا فشل الطلب، نرمي خطأ حتى تتعامل React Query معه.
  • عند النجاح نستخدم onSuccess لتحديث البيانات المعروضة.
  • invalidateQueries تجبر استعلام ["users"] على إعادة الجلب.

بهذه الطريقة تبقى الواجهة محدثة تلقائيًا — بدون تحديث الصفحة.

ملاحظة: React Query لا يفرض طريقة جلب معينة — يمكنك استخدام Fetch أو Axios أو أي عميل HTTP. لهذا السبب يُذكر دائمًا ضمن مقارنة React Query vs Fetch و React Query vs Axios.

أفضل الممارسات المختصرة

  • اجعل queryKey واضحًا ومعبّرًا مثل ["users", id].
  • استخدم staleTime لتقليل الطلبات غير الضرورية.
  • اعزل منطق الجلب في دوال مستقلة لسهولة الاختبار وإعادة الاستخدام.

Pagination و Infinite Query في React Query

في صفحات طويلة أو قوائم غير منتهية، ستحتاج إلى React Query pagination أو React Query infinite query. الفكرة أنك تجلب صفحات صغيرة وتضيفها تدريجيًا بدل تحميل كل شيء دفعة واحدة.

import { useInfiniteQuery } from "@tanstack/react-query";

function Posts() {
  const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({
    queryKey: ["posts"],
    queryFn: ({ pageParam = 1 }) =>
      fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}&_limit=10`)
        .then((res) => res.json()),
    getNextPageParam: (lastPage, allPages) => allPages.length + 1,
  });

  return (
    <div>
      {data?.pages.flat().map((p) => (
        <p key={p.id}>{p.title}</p>
      ))}
      {hasNextPage && (
        <button onClick={() => fetchNextPage()}>
          {isFetching ? "جاري التحميل..." : "تحميل المزيد"}
        </button>
      )}
    </div>
  );
}

شرح خطوة بخطوة للمبتدئ جدًا:

  • استعملنا useInfiniteQuery بدل useQuery لأنه يدعم الصفحات المتعددة.
  • queryKey يعطي هوية ثابتة للكاش.
  • queryFn يستقبل pageParam ليعرف أي صفحة يطلب.
  • كل ضغطة على “تحميل المزيد” تنفذ fetchNextPage().
  • getNextPageParam يحدد رقم الصفحة التالية بناءً على عدد الصفحات الحالية.
  • النتيجة تُخزن في data.pages، لذلك ندمجها بـ flat().

هذا الأسلوب مثالي لـ React Query pagination و React Query infinite query.

React Query Devtools (لمراقبة الكاش)

أثناء التطوير يمكنك تفعيل React Query devtools لرؤية الكاش والاستعلامات الحالية. هذا يساعدك على فهم سلوك queryKey و staleTime بسرعة.

import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

شرح مفصل خطوة بخطوة:

  • نستورد ReactQueryDevtools من الحزمة الخاصة بها.
  • نضيفها داخل QueryClientProvider حتى ترى نفس الكاش.
  • initialIsOpen={false} يعني أن اللوحة تبدأ مغلقة.

ببساطة: Devtools لوحة تعرض لك الكاش والاستعلامات. سترى queryKey الحالي، وهل البيانات stale أو fresh، وهذا يساعدك على فهم سلوك React Query بسرعة.

الأسئلة الشائعة — FAQ

ما هو React Query؟

مكتبة لإدارة جلب البيانات مع كاش وحالات تحميل وأخطاء جاهزة.

هل أحتاج React Query مع Fetch أو Axios؟

نعم، لأنها تضيف كاش وإعادة جلب وتحكم متقدم بالحالة.

ما الفرق بين isLoading و isFetching؟

isLoading لأول تحميل، وisFetching لإعادة الجلب في الخلفية.

متى أستخدم useMutation؟

عند إرسال بيانات أو تعديلها مثل POST/PUT/DELETE.

هل React Query مناسب للمشاريع الصغيرة؟

نعم إذا لديك بيانات مشتركة بين أكثر من شاشة.

التالي: سننتقل لدرس الأخطاء وحالات التحميل لتحسين تجربة المستخدم.
المحرر الذكي

اكتب الكود وشاهد النتيجة فوراً

جرب الآن مجاناً
قناة ديف عربي

تابع أحدث الدروس والتحديثات مباشرة على واتساب

انضم الآن