_

بواسطة |

للرجوع
12 دقائق للقراءة
٢٠ مايو ٢٠٢٥

مقدمة لـ TanStack Query: طريقة سهلة للتعامل مع بيانات الـ API في React

جلب البيانات من السيرفر عن طريق API او حل اخر في تطبيقات React والويب بشكل عام من أكثر المهام المتكررة، لكن إدارتها بشكل صحيح — من تحميل، التعامل مع أخطاء، والتحديث تلقائي، وتعديل البيانات — قد يكون اصعب كلما كبر المشروع.

هنا تأتي الحاجه لمكتبة مثل TanStack Query (المعروفة سابقًا بـ React Query) كحل ذكي واحترافي للتعامل مع البيانات بدون تعقيد.

سنتعرف في هذا المقال عن:

  • ماهي TanStack Query ؟
  • طريقة تثبيتها
  • الطريقة التقليدية لجلب البيانات
  • اهم المفاهيم وكيف نستخدمها صح
  • حالات البيانات وكيف نتعامل بناء عليها
  • متى يمكن ماتحتاجها (لو كنت تستخدم Server components)

ماهي TanStack Query ؟

TanStack Query هي مكتبه قوية من مجموعة اشخاص لهم مساهمات قويه بتقنيات الويب بشكل عام كانت تعرف مسبقا ب React query معنيه بتسهيل جلب البيانات من السيرفر وتيسير تحميلها والتعامل مع اخطائها وتخزينها بشكل مؤقت وتحديثها المستمر. تعتبر مكتبه مرنه للاستخدام مع اغلب اطارات الويب مثل Vue, Angular, Solid و Svelte.

اهم المزايا:

  • caching ذكي للبيانات بحيث يتم تخزينها بشكل مؤقت.

  • Background refetching بحيث تقدر تسوي استدعاء وجلب للبيانات بالخلفيه بشكل مستمر وتلقائي.

  • Pagination, infinite scroll تدعم جلب البيانات بشكل اصغر بدال جلبها كلها بنفس الوقت وايضا جلبها عند الحاجه لها.

  • Easy mutation handling بحيث تقدر تسوي تعديل على البيانات سواء اضافه او تعديل او حذف POST و PATCH و DELETE بشكل سهل.


طريقة تثبيتها

لو كنت تستخدم NPM

npm install @tanstack/react-query

لو كنت تستخدم CDN

<script type="module">
  import React from 'https://esm.sh/react@18.2.0'
  import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
  import { QueryClient } from 'https://esm.sh/@tanstack/react-query'
</script>

تحتاج تضيف هالشي بأول ملف للمشروع

const queryClient = new QueryClient()

function App() {
  return (
    // Provide the client to your App
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  )
}

من هنا كل شيء جاهز عشان تستخدم TanStack Query


الطريقة التقليدية لجلب البيانات

بنقارن هنا كيف نجلب البيانات بالشكل التقليدي المعتاد بدون TanStack Query وكيف نجلبها مع TanStack Query.

بدون TanStack Query :

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
 const fetchData = async () => {
   try {
     const res = await fetch('/api/posts');
     const json = await res.json();
     setData(json);
   } catch (err) {
     setError(err);
   } finally {
     setLoading(false);
   }
 };
 fetchData();
}, []);

return (
 <div>
   {loading && <p>Loading...</p>}
   {error && <p>Error: {error.message}</p>}
   {data && <p>{data.length} posts</p>}
 </div>
);

مشاكلها:

  • كود كثير واعتماد كثير على useState
  • مافيه caching تخزين و retries اعادة محاوله بحالة الفشل.
  • مافيه background refetch اللي بيساعد بتحديث البيانات بالخلفيه بشكل مستمر وتلقائي
  • ماهي scalable للتطبيقات الكبيره ولو كبر حجم البيانات.

طبعا اغلب نقاط الضعف هذي تقدر تتفادها بس بتحتاج تعيد العجله وتكتب كود كثير احد غيرك مسوي حولها مكتبه جاهزه لك زي TanStack Query.

مع TanStack Query :

const { data, isLoading, error } = useQuery({
  queryKey: ['posts'],
  queryFn: () => fetch('/api/posts').then(res => res.json()),
});

if(isLoading) return <p>Loading...</p>
if(error) return <p>Error: {error.message}</p>
if(data) return <p>{data.length} posts</p>

المزايا :

  • مافيه كود كثير
  • التعامل مع البيانات بتحميلها واخطائها او بحالة النجاح مبني وجاهز
  • جاهز ومبني caching تخزين و retries اعادة محاوله بحالة الفشل.
  • موجود ومبني background refetch اللي بيساعد بتحديث البيانات بالخلفيه بشكل مستمر وتلقائي
  • تعديل البيانات بحذفها او اضافتها او تعديلها سلس ومبني
  • حل ممتاز بحالة تغير حجم المشروع مستقبلا

اهم المفاهيم

1. Queries جلب البيانات

const { data, isLoading, error } = useQuery({
  queryKey: ['posts'],
  queryFn: () => fetch('/api/posts').then(res => res.json()),
});

  • queryKey : معرف خاص للبيانات مثل userData او articleData
  • queryFn : هذي async function تسوي الاستدعاء للapi سواء تستخدم Fetch او Axios.

مثال تفاعلي:

import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import { useState } from 'react';

const queryClient = new QueryClient();

function TodoList() {
const { data, isLoading, error } = useQuery({
queryKey: ['todos'],
queryFn: async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');
  return response.json();
},
});

if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;

return (
<div>
  <h2>Todo List</h2>
  <ul>
    {data?.map((todo: any) => (
      <li key={todo.id}>{todo.title}</li>
    ))}
  </ul>
</div>
);
}

export default function App() {
return (
<QueryClientProvider client={queryClient}>
  <div style={{ padding: '20px' }}>
    <h1>TanStack Query Example</h1>
    <TodoList />
  </div>
</QueryClientProvider>
);
}
  1. Mutations التعديل على البيانات سواء POST PATCH او DELETE

const mutation = useMutation({
  mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

مثال تفاعلي:

import { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';

const queryClient = new QueryClient();

function TodoApp() {
const queryClient = useQueryClient();
const [newTodo, setNewTodo] = useState('');

const { data: todos, isLoading } = useQuery({
queryKey: ['todos'],
queryFn: async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');
  return response.json();
},
});

const mutation = useMutation({
mutationFn: async (newTodo: string) => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos', {
    method: 'POST',
    body: JSON.stringify({
      title: newTodo,
      completed: false,
      userId: 1,
    }),
    headers: {
      'Content-type': 'application/json; charset=UTF-8',
    },
  });
  return response.json();
},
onSuccess: () => {
  queryClient.invalidateQueries({ queryKey: ['todos'] });
  setNewTodo('');
},
});

if (isLoading) return <div>Loading...</div>;

return (
<div>
  <h2>Todo App with Mutations</h2>
  <div style={{ marginBottom: '20px' }}>
    <input
      value={newTodo}
      onChange={(e) => setNewTodo(e.target.value)}
      placeholder="Add new todo"
      style={{ marginRight: '10px', padding: '5px' }}
    />
    <button
      onClick={() => mutation.mutate(newTodo)}
      disabled={!newTodo || mutation.isPending}
      style={{ padding: '5px 10px' }}
    >
      {mutation.isPending ? 'Adding...' : 'Add Todo'}
    </button>
  </div>
  {mutation.isError && <div style={{ color: 'red' }}>Error: {mutation.error.message}</div>}
  {mutation.isSuccess && <div style={{ color: 'green' }}>Todo added successfully!</div>}
  <ul>
    {todos?.map((todo: any) => (
      <li key={todo.id}>{todo.title}</li>
    ))}
  </ul>
</div>
);
}

export default function App() {
return (
<QueryClientProvider client={queryClient}>
  <div style={{ padding: '20px' }}>
    <h1>TanStack Query Mutations</h1>
    <TodoApp />
  </div>
</QueryClientProvider>
);
}

تستخدم هذي لو بتحدث البيانات او تحذفها او بتسوي شيء جديد.

  1. Query Invalidation
const queryClient = useQueryClient();

queryClient.invalidateQueries({ queryKey: ['todos'] });

تستخدمها عشان تجدد البيانات بحالة التغيير بإستخدامMutations

  1. Infinite Queries

useInfiniteQuery({
  queryKey: ['feed'],
  queryFn: ({ pageParam = 1 }) => fetchFeed(pageParam),
  getNextPageParam: (lastPage) => lastPage.nextCursor,
});

مثال تفاعلي:

import { QueryClient, QueryClientProvider, useInfiniteQuery } from '@tanstack/react-query';
import { useState } from 'react';

const queryClient = new QueryClient();

function InfiniteTodoList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
status
} = useInfiniteQuery({
queryKey: ['infiniteTodos'],
queryFn: async ({ pageParam = 1 }) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos?_page=${pageParam}&_limit=5`
  );
  return response.json();
},
getNextPageParam: (lastPage, allPages) => {
  return lastPage.length === 5 ? allPages.length + 1 : undefined;
},
});

if (status === 'pending') return <div>Loading...</div>;
if (status === 'error') return <div>Error loading todos</div>;

return (
<div>
  <h2>Infinite Todo List</h2>
  <ul>
    {data.pages.map((page, i) => (
      <div key={i}>
        {page.map((todo: any) => (
          <li key={todo.id} style={{ marginBottom: '10px' }}>
            {todo.title}
          </li>
        ))}
      </div>
    ))}
  </ul>
  <div style={{ marginTop: '20px' }}>
    <button
      onClick={() => fetchNextPage()}
      disabled={!hasNextPage || isFetchingNextPage}
      style={{
        padding: '8px 16px',
        backgroundColor: hasNextPage ? '#4CAF50' : '#cccccc',
        color: 'white',
        border: 'none',
        borderRadius: '4px',
        cursor: hasNextPage ? 'pointer' : 'not-allowed'
      }}
    >
      {isFetchingNextPage
        ? 'Loading more...'
        : hasNextPage
        ? 'Load More'
        : 'Nothing more to load'}
    </button>
  </div>
</div>
);
}

export default function App() {
return (
<QueryClientProvider client={queryClient}>
  <div style={{ padding: '20px' }}>
    <h1>TanStack Query Infinite Query Example</h1>
    <InfiniteTodoList />
  </div>
</QueryClientProvider>
);
}

لو بتدعم جلب البيانات بشكل صغير بدال جلبها كلها بنفس الوقت


الحالات الأساسية (States) في TanStack Query للبيانات

كل استعلام (Query) أو عملية تعديل (Mutation) لها حالات تساعدك تتحكم بالواجهة وتعرف وش يصير عشان تتعامل معه بشكل خاص.

  • isLoading: هذي الحالة تكون true إذا البيانات قاعده تتحمل لأول مرة.
const { isLoading } = useQuery(...);

if (isLoading) return <p>جاري التحميل...</p>;

  • isFetching: تكون true لو البيانات قاعدة تتحدث في الخلفية (يعني فيه refetch).
const { isFetching } = useQuery(...);

if (isFetching) return <p>جاري التحميل...</p>;

  • isError و error: لو كان فيه خطأ او عدة اخطاء عشان تتعامل بناء على ذلك. ممكن تظهر تنبيه مثلا.

  • isSuccess: تكون true إذا البيانات وصلت بدون مشاكل.

const { isSuccess } = useQuery(...);

if (isSuccess) return <p>تم التحميل بنجاح</p>;


طيب، نحتاج TanStack Query لو نستخدم Server Components ؟

✅ مع Next.js والـ App Router، ممكن تجيب البيانات من السيرفر مباشرة

في الحالة هذي:

  • ما تحتاج TanStack Query

  • الصفحة تجي جاهزة مع البيانات

  • مافي حاجة لloading أو fetch من المتصفح

متى نرجع نحتاج TanStack Query ؟

  • لو بيكون فيه تفاعل مباشر بعد تحميل الصفحة (زي فلترة، بحث)

  • لو تبغى تحديث مباشر للبيانات (مثلاً إشعارات أو محادثه)

  • لو تبغى تسوي Optimistic Update (تحديث واجهة المستخدم مباشرة قبل ما تخلص العملية).

  • لو تحتاج تشارك البيانات بين أكثر مكان بسهولة.


الخاتمة

إذا كنت تبني تطبيق بـ React وتتعامل كثير مع APIs، فـ TanStack Query راح توفر عليك وقت وجهد كبير. هي مب بس مكتبة تجيب البيانات، لكنها أداة ذكية تساعدك تدير كل شيء يخص بيانات السيرفر بطريقة احترافية وسهلة.


المصادر

اشترك في النشرة البريدية

احصل على آخر المقالات والتحديثات مباشرة في بريدك الإلكتروني