Mojtaba Pourkhanlar
About meProjectsBlog

  • 👤About me
  • 🧰Projects
  • ✍️Blog

React Query (TanStack Query)


کتابخانه React Query یکی از قدرتمندترین ابزارها برای مدیریت "Server State" در React است — یعنی داده‌هایی که از سرور می‌گیریم و باید بین رندرها، کش، به‌روزرسانی و کنترلشون کنیم.

فهرست مطالب


  1. مفاهیم پایه
  2. useQuery
  3. useInfiniteQuery
  4. useMutation
  5. Query Keys
  6. Query Client
  7. Cache & Invalidation
  8. Status & State
  9. Options
  10. Patterns
  11. Best Practices

1. مفاهیم پایه


Query

  • درخواست خواندن دیتا از سرور (مثل GET /posts) برای گرفتن اطلاعات:
useQuery({
  queryKey: ["posts"]
,
  queryFn: () => fetch("/api/posts").then(res => res.json()),
});

Mutation

  • درخواست تغییر دیتا روی سرور (POST, PUT, DELETE)
useMutation({
  mutationFn: newPost =>
    fetch("/api/posts", {
      method: "POST",
      body: JSON.stringify(newPost),
    }),
});

Cache

  • در این بخش React Query خودش داده رو Cache می‌کنه تا درخواست‌های تکراری سریع‌تر برگردن:
const postQuery = useQuery({ queryKey: ["post", id]
, queryFn: getPost });
console.log(postQuery.data); // داده از cache در دسترس

Query Key

  • شناسه یکتایی که برای مدیریت cache استفاده میشه:
["posts", pageIndex, filters]
;

اگر یکی از این‌ها تغییر کنه، React Query می‌فهمه که باید دوباره دیتا رو fetch کنه.

2. useQuery


const { data, isLoading, isError, refetch } = useQuery({
  queryKey: ["users"]
,
  queryFn: async () => {
    const res = await fetch("/api/users");
    if (!res.ok) throw new Error("Failed to fetch");
    return res.json();
  },
  staleTime: 60 * 1000, // داده تا 1 دقیقه fresh میمونه
  cacheTime: 5 * 60 * 1000, // داده 5 دقیقه در حافظه می‌مونه
  retry: 3, // حداکثر 3 بار retry
  refetchOnWindowFocus: false,
  // enabled
});

اگر enabled: false باشه، کوئری فقط موقع refetch() اجرا میشه.

Option توضیح
queryKey کلید یکتای کوئری
queryFn تابع async
enabled فعال/غیرفعال
staleTime زمان fresh
cacheTime زمان نگهداری
refetchOnWindowFocus refetch فوکوس
retry تعداد retry
select تغییر شکل دیتا
onSuccess بعد از موفقیت
onError بعد از خطا

3. useInfiniteQuery


  • برای پیاده‌سازی صفحه‌بندی نامتناهی (Infinite Scroll).
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery({
    queryKey: ["postsInfinite"]
,
    queryFn: async ({ pageParam = 1 }) =>
      fetch(`/api/posts?page=${pageParam}`).then(res => res.json()),

    getNextPageParam: (lastPage, allPages) =>
      lastPage.nextPage ? lastPage.nextPage : undefined,
  });
  • Options
Option توضیح
queryKey کلید
queryFn دریافت صفحه
initialPageParam شروع
getNextPageParam صفحه بعد
  • Property
Property توضیح
data.pages صفحات
fetchNextPage لود بعدی
hasNextPage وجود صفحه
isFetchingNextPage لود صفحه

استفاده در کامپوننت:

return (
  <div>
    {data?.pages.map((page, i) => (
      <React.Fragment key={i}>
        {page.items.map(post => (
          <p key={post.id}>{post.title}</p>
        ))}
      </React.Fragment>
    ))}

    <button onClick={() => fetchNextPage()} disabled={!hasNextPage}>
      {isFetchingNextPage ? "Loading..." : "Load More"}
    </button>
  </div>
);

4. useMutation


  • برای ارسال یا تغییر داده (ثبت فرم، حذف آیتم، و …)
const mutation = useMutation({
  mutationFn: async newPost => {
    const res = await fetch("/api/posts", {
      method: "POST",
      body: JSON.stringify(newPost),
    });
    return res.json();
  },
  onSuccess: () => {
    // invalidate برای بروز‌رسانی لیست
    queryClient.invalidateQueries({ queryKey: ["posts"]
 });
  },
  onError: () => console.error("خطا در ارسال داده"),
});
  • Options مهم
Option توضیح
mutationFn تابع mutation
onMutate قبل ارسال
onSuccess موفق
onError خطا
onSettled همیشه

استفاده در event:

function handleAddPost() {
  mutation.mutate({ title: "New Post", content: "..." });
}

5. Query Keys


  • کلید یکتای هر query باید معنی‌دار و ساخت‌یافته باشه:
["projects", pageIndex, pageSize]
[("projects", { pageIndex, pageSize, alias })]
;

تغییر هر بخش از Query Key باعث invalidate و fetch جدید میشه.

6. Query Client


  • ابزار کنترلی برای تعامل مستقیم با cache.
const queryClient = useQueryClient();

// invalidate and refetch
queryClient.invalidateQueries({ queryKey: ["users"]
 });

// دسترسی مستقیم به cache
const cachedData = queryClient.getQueryData(["users"]
);
  • دیگر متدها:
Method توضیح
invalidateQueries نشانه‌گذاری کوئری برای refetch
refetchQueries فورس refetch
setQueryData آپدیت مستقیم cache
getQueryData گرفتن cache
removeQueries حذف داده‌های cache
cancelQueries لغو درخواست‌های در حال اجرا

7. Cache & Invalidation


  • همانطور که بالاتر گفتیم React Query به طور هوشمند cache رو مدیریت می‌کنه:
queryClient.invalidateQueries(["users"]
); // داده قدیمی میشه (stale)
queryClient.removeQueries(["posts"]
); // حذف کامل cache
queryClient.setQueryData(["post", id]
, newData); // optimistic update

وقتی invalidate انجام می‌دی، React Query خودش دوباره fetch می‌کنه تا داده تازه بشه.

8. Status and State


  • هر کوئری مجموعه‌ای از flagها داره که وضعیتش رو نشون میده:
Status توضیح
isLoading اولین بار داده گرفته میشه
isFetching در حال گرفتن داده جدید
isPending mutation در حال اجرا
isSuccess درخواست موفق
isError خطا در درخواست
  • مثال ساده:
if (isLoading) return <Spinner />;
if (isError) return <Error />;
return <UserList data={data} />;

9. Options مهم


staleTime

  • داده تا مدتی fresh باقی می‌مونه و تا اون زمان دوباره fetch نمیشه:
staleTime: 1000 * 60 * 5; // ۵ دقیقه

retry

  • تعداد تلاش مجدد در صورت خطا:
retry: 3;

10. Patterns


Pagination

  • صفحه‌بندی ساده با useQuery و pageIndex در queryKey:
const { data } = useQuery({
  queryKey: ["posts", pageIndex]
,
  queryFn: () => fetch(`/api/posts?page=${pageIndex}`).then(res => res.json()),
});

Infinite Scroll

  • از useInfiniteQuery برای اسکرول بی‌نهایت:
fetchNextPage();

Conditional Fetch

  • فقط Fetch زمانی انجام بشه که شرط برقرار باشه:
enabled: !!id;

11. Best Practices


  • استفاده از queryKey دقیق و معنی‌دار

  • انجام invalidate هدفمند بعد از mutation

  • منطق fetch داخل hook نه داخل کامپوننت

  • استفاده از optimistic update برای UX بهتر

  • انجام mutation مستقیم در component‌ها


جمع‌بندی

خب دیدید که React Query مدیریت پیچیده‌ی state سرور رو به زیباترین و کارآمدترین شکل ممکن ساده کرده. با این ابزار، دغدغه‌های caching، refetch، error handling و pagination وقتی مثل قبل خسته‌کننده نیستند

یه بار درست کانفیگش کن، بعدش فقط لذت ببر از اپلیکیشنی که بدون reload همیشه تازه‌ست!