🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

دليل استعلام TanStack 2026: جلب بيانات التفاعل والطفرات والتخزين المؤقت

⏱️4 min read  ·  785 words

يعد TanStack Query (المعروف سابقًا باسم React Query) هو المعيار الذهبي لإدارة حالة الخادم في تطبيقات React وVue وSolid وAngular في عام 2026. فهو يزيل تعقيد جلب البيانات يدويًا والتخزين المؤقت والمزامنة، ويستبدل مئات الأسطر من كود useEffect بخطافات بسيطة وقوية.

لماذا استعلام TanStack؟

  • لا يوجد طبق معياري– يستبدل أنماط Redux + fetch + useEffect
  • التخزين المؤقت التلقائي— البيانات المخزنة مؤقتًا بواسطة المفتاح، ويتم تحديثها عند الحاجة
  • إعادة جلب الخلفية– تظهر البيانات القديمة على الفور، والبيانات الجديدة في الخلفية
  • حالات التحميل/الخطأ— مدمج، ولا توجد إدارة يدوية للحالة
  • تحديثات متفائلة– تحديثات واجهة المستخدم قبل تأكيد الخادم
  • أدوات التطوير– فحص ذاكرة التخزين المؤقت، إعادة الجلب، إبطال امتداد المتصفح

التثبيت والإعداد

npm install @tanstack/react-query @tanstack/react-query-devtools

# Optional: axios for HTTP
npm install axios

// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,    // 5 minutes (data considered fresh)
      gcTime: 10 * 60 * 1000,      // 10 minutes (cache garbage collection)
      retry: 2,                     // retry failed requests twice
      refetchOnWindowFocus: true,   // refetch when tab becomes active
    },
  },
});

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

useQuery – جلب البيانات

import { useQuery, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

interface User {
  id: number;
  name: string;
  email: string;
}

// Query function
async function fetchUser(id: number): Promise<User> {
  const { data } = await axios.get(`/api/users/${id}`);
  return data;
}

// Component using query
function UserProfile({ userId }: { userId: number }) {
  const {
    data: user,
    isLoading,
    isError,
    error,
    isFetching,        // true when refetching in background
    isStale,           // true when data is stale
    refetch,           // manually trigger refetch
  } = useQuery({
    queryKey: ['users', userId],    // unique cache key
    queryFn: () => fetchUser(userId),
    enabled: userId > 0,            // only fetch when userId is valid
    staleTime: 60_000,              // fresh for 1 minute
    select: (data) => data,         // transform data before returning
  });

  if (isLoading) return <Skeleton />;
  if (isError) return <ErrorBoundary error={error} />;

  return (
    <div>
      {isFetching && <small>Refreshing...</small>}
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button onClick={() => refetch()}>Refresh</button>
    </div>
  );
}

الاستعلامات الموازية

// Fetch multiple queries simultaneously
function Dashboard() {
  const [userQuery, postsQuery, statsQuery] = useQueries({
    queries: [
      { queryKey: ['user', 1], queryFn: () => fetchUser(1) },
      { queryKey: ['posts'], queryFn: fetchPosts },
      { queryKey: ['stats'], queryFn: fetchStats },
    ],
  });

  if (userQuery.isLoading || postsQuery.isLoading) return <Loading />;

  return (
    <div>
      <UserCard user={userQuery.data} />
      <PostList posts={postsQuery.data} />
      <StatsPanel stats={statsQuery.data} />
    </div>
  );
}

useMutation — الإنشاء والتحديث والحذف

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

async function createPost(post: { title: string; content: string }) {
  const { data } = await axios.post('/api/posts', post);
  return data;
}

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

  const { mutate, isPending, isError, error } = useMutation({
    mutationFn: createPost,

    // Called when mutation succeeds
    onSuccess: (newPost) => {
      // Option 1: Invalidate cache (triggers refetch)
      queryClient.invalidateQueries({ queryKey: ['posts'] });

      // Option 2: Update cache directly (no refetch)
      queryClient.setQueryData(['posts'], (old: Post[]) => [...old, newPost]);

      // Show toast notification
      toast.success('Post created!');
    },

    onError: (error) => {
      toast.error(`Failed: ${error.message}`);
    },
  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;
    mutate({
      title: (form.title as HTMLInputElement).value,
      content: (form.content as HTMLTextAreaElement).value,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" required placeholder="Title" />
      <textarea name="content" required placeholder="Content" />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Creating...' : 'Create Post'}
      </button>
      {isError && <p className="error">{error.message}</p>}
    </form>
  );
}

تحديثات متفائلة

const { mutate: likePost } = useMutation({
  mutationFn: (postId: number) => axios.post(`/api/posts/${postId}/like`),

  onMutate: async (postId) => {
    // Cancel any outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['posts', postId] });

    // Snapshot the previous value
    const previousPost = queryClient.getQueryData(['posts', postId]);

    // Optimistically update the UI
    queryClient.setQueryData(['posts', postId], (old: Post) => ({
      ...old,
      likes: old.likes + 1,
    }));

    // Return context for rollback
    return { previousPost };
  },

  // Roll back on error
  onError: (error, postId, context) => {
    queryClient.setQueryData(['posts', postId], context?.previousPost);
    toast.error('Failed to like post');
  },

  // Always refetch after mutation
  onSettled: (data, error, postId) => {
    queryClient.invalidateQueries({ queryKey: ['posts', postId] });
  },
});

استعلامات لا نهاية لها

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

function InfinitePostList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['posts', 'infinite'],
    queryFn: ({ pageParam = 1 }) => fetchPosts(pageParam),
    initialPageParam: 1,
    getNextPageParam: (lastPage, pages) =>
      lastPage.hasMore ? pages.length + 1 : undefined,
  });

  return (
    <div>
      {data?.pages.map((page, i) => (
        <React.Fragment key={i}>
          {page.posts.map(post => <PostCard key={post.id} post={post} />)}
        </React.Fragment>
      ))}
      <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
        {isFetchingNextPage ? 'Loading...' : hasNextPage ? 'Load More' : 'All loaded'}
      </button>
    </div>
  );
}

خطافات الاستعلام المخصصة

// hooks/useUsers.ts — reusable query hooks
export function useUsers(filters?: UserFilters) {
  return useQuery({
    queryKey: ['users', filters],
    queryFn: () => fetchUsers(filters),
    select: (data) => ({
      users: data.users,
      total: data.total,
    }),
  });
}

export function useUser(id: number) {
  const queryClient = useQueryClient();
  return useQuery({
    queryKey: ['users', id],
    queryFn: () => fetchUser(id),
    // Pre-populate from list query
    initialData: () => {
      const usersQuery = queryClient.getQueryData<UsersResponse>(['users']);
      return usersQuery?.users.find(u => u.id === id);
    },
    initialDataUpdatedAt: () =>
      queryClient.getQueryState(['users'])?.dataUpdatedAt,
  });
}

export function useUpdateUser() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (user: Partial<User> & { id: number }) =>
      axios.patch(`/api/users/${user.id}`, user).then(r => r.data),
    onSuccess: (updated) => {
      queryClient.setQueryData(['users', updated.id], updated);
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

يزيل استعلام TanStack في عام 2026 80% من النموذج النموذجي في كود جلب بيانات React النموذجي. ابدأ باستخدام useQuery للقراءة، وuseMutation للكتابة. استخدم validateQueries لتحديثات ذاكرة التخزين المؤقت البسيطة وsetQueryData للحصول على تحديثات متفائلة. الاستثمار في التعلم يؤتي ثماره في كل مشروع.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা