React Query (TanStack Query)
کتابخانه React Query یکی از قدرتمندترین ابزارها برای مدیریت "Server State" در React است — یعنی دادههایی که از سرور میگیریم و باید بین رندرها، کش، بهروزرسانی و کنترلشون کنیم.
فهرست مطالب
- مفاهیم پایه
- useQuery
- useInfiniteQuery
- useMutation
- Query Keys
- Query Client
- Cache & Invalidation
- Status & State
- Options
- Patterns
- 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 وقتی مثل قبل خستهکننده نیستند