মনে করুন আপনি একটি ইকমার্স অ্যাপ বানিয়েছেন React বা React-এর উপর ভিত্তি করে বানানো যেকোনো মেটা ফ্রেমওয়ার্ক (যেমন Next.js) দিয়ে। UI যেমন চেয়েছিলেন তেমনই হয়েছে—কিন্তু ছোটখাটো জিনিস আপনাকে খুঁতখুঁতে করে তুলছে:
- একই প্রোডাক্ট লিস্ট দুটি কম্পোনেন্টে দেখাতে গিয়ে API দুবার কল হয়ে গেছে।
- ইউজার অন্য ট্যাবে গিয়ে ফিরলে অ্যাপ মনে করে সবকিছু আবার শূন্য থেকে আনতে হবে।
- কোনো এক API একবার error throw করলে সেই API-এর ওপর নির্ভরশীল UI ভেঙে গেল, আর স্বয়ংক্রিয় retry বা পুনরুদ্ধার হল না।
এগুলা কে আপনি বাগ বলবেন? আসলে এগুলো বেশিরভাগ সময় “বাগ” নয়; এগুলো হলো সার্ভার স্টেট—অর্থাৎ সার্ভারে যা আছে সেটাকে UI-তে ধরে রাখা, ফ্রেশ রাখা আর ভুল হলে সামলানো বা fault tolerance যেটা useState আর useEffect দিয়ে নিজে নিজে করতে গেলে অনেক হেভি কাজ হয়ে যাবে।
TanStack Query (আগের নাম React Query) এসেছে এই সমস্যা সমাধানের কাজটার জন্য। এটি শুধু “ডাটা আনা” নয়—ক্যাশ, রিট্রাই, রিফেচ, ডিডুপ্লিকেশন, মিউটেশন—এক জায়গায় গুছিয়ে দেয়, যাতে কম্পোনেন্টে থাকে বেশি business লজিক, কম প্লাম্বিং।
নিচের কোড ব্লকটি লক্ষ করুন:
React-এ data fetch করার সাধারণ উপায় হলো useEffect আর useState দিয়ে:
function ProductList() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("/api/products")
.then((res) => res.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((err) => {
setError(err);
setLoading(false);
});
}, []);
if (loading) return <p>লোড হচ্ছে...</p>;
if (error) return <p>সমস্যা হয়েছে!</p>;
return <div>{data?.map(...)}</div>;
}
এই কোড কাজ করছে, অর্থাৎ ডাটা আনছে, লোডিং ম্যানেজ করছে, ইরর স্টেট রাখছে। কিন্তু এখানে অনেক কিছু নেই — caching নেই, background refetch নেই, retry নেই, pagination নেই। প্রতিটা component-এ একই boilerplate বারবার লিখতে হচ্ছে। বেশিরভাগ UI ফ্রেমওয়ার্ক এখানে নীরব; ফলে এক দল নিজের লাইব্রেরি বানায়, বাকিরা প্রতি ফিচারে একই ধরনের useEffect আবার লেখে।
উপরে যে লাইব্রেরির কথা বললাম, সেটা ঠিক এই ফাঁকটা পূরণ করে—fetching, ক্যাশ, সিঙ্ক আর মিউটেশন একই মানসিক মডেলে এনে reducers, টাইমার আর হোমকুক রিট্রাই লজিক না লিখেও একই কাজ ছোট, পড়তে সুবিধে এমন API-তে গুছিয়ে দেয়।
TanStack Query কী কী দেয়?
এর feature set-এ আছে: Auto Caching, Auto Refetching, Window Focus Refetching, Polling/Realtime Queries, Parallel Queries, Dependent Queries, Mutations API, Automatic Garbage Collection, Paginated/Cursor Queries, Infinite Scroll, Request Cancellation, Suspense Support, Prefetching, Offline Support, SSR Support এবং আরও অনেক কিছু।
একটা গুরুত্বপূর্ণ কথা মনে রাখতে হবে — TanStack Query backend agnostic। অর্থাৎ এটি যেকোনো async function-এর সাথে কাজ করে। REST API, GraphQL, সরাসরি DB call, localStorage, IndexedDB, এমনকি setTimeout — যেকোনো Promise return করে যেকোনো কিছুর সাথেই ব্যবহার করা যায়।
সেটআপ
npm install @tanstack/react-query @tanstack/react-query-devtools
// app/providers.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { useState } from "react";
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 মিনিট fresh থাকবে
retry: 2, // error হলে 2 বার retry
},
},
}),
);
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
মূল ধারণা: queryKey এবং queryFn
TanStack Query-র দুটি মূল জিনিস:
queryKey — একটি array যা query-কে uniquely identify করে। এটি cache-এর key হিসেবে কাজ করে। Key বদলালে নতুন fetch হয়।
queryFn — যেকোনো async function যেটা Promise return করে।
useQuery({
queryKey: ["products"], // cache key
queryFn: () => fetchProducts(), // যেকোনো async function
});
Part 1 — REST API Call
সাধারণ GET Request
// hooks/useProducts.ts
import { useQuery } from "@tanstack/react-query";
type Product = {
id: number;
name: string;
price: number;
};
const fetchProducts = async (): Promise<Product[]> => {
const res = await fetch("/api/products");
if (!res.ok) throw new Error("পণ্য লোড করতে সমস্যা হয়েছে");
return res.json();
};
export function useProducts() {
return useQuery({
queryKey: ["products"],
queryFn: fetchProducts,
staleTime: 5 * 60 * 1000, // 5 মিনিট cache fresh থাকবে
});
}
// components/ProductList.tsx
"use client";
import { useProducts } from "@/hooks/useProducts";
export function ProductList() {
const { data, isPending, isError, error, isFetching } = useProducts();
if (isPending) return <p>পণ্য লোড হচ্ছে...</p>;
if (isError) return <p>সমস্যা: {error.message}</p>;
return (
<div>
{/* isFetching মানে background-এ নতুন data আসছে */}
{isFetching && <span>আপডেট হচ্ছে...</span>}
{data.map((product) => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>৳{product.price}</p>
</div>
))}
</div>
);
}
Dynamic queryKey — ID দিয়ে Single Item
// hooks/useProduct.ts
const fetchProduct = async (id: number) => {
const res = await fetch(`/api/products/${id}`);
if (!res.ok) throw new Error("পণ্য পাওয়া যায়নি");
return res.json();
};
export function useProduct(id: number) {
return useQuery({
queryKey: ["products", id], // id বদলালে নতুন fetch হবে
queryFn: () => fetchProduct(id),
enabled: !!id, // id না থাকলে fetch করবে না
});
}
POST/PUT/DELETE — useMutation
// hooks/useCreateProduct.ts
import { useMutation, useQueryClient } from "@tanstack/react-query";
type NewProduct = { name: string; price: number };
const createProduct = async (product: NewProduct) => {
const res = await fetch("/api/products", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(product),
});
if (!res.ok) throw new Error("তৈরি করতে সমস্যা হয়েছে");
return res.json();
};
export function useCreateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createProduct,
onSuccess: () => {
// সফল হলে products cache invalidate করো — নতুন data আসবে
queryClient.invalidateQueries({ queryKey: ["products"] });
},
onError: (error) => {
console.error("পণ্য তৈরিতে সমস্যা:", error);
},
});
}
// components/AddProductForm.tsx
"use client";
import { useCreateProduct } from "@/hooks/useCreateProduct";
import { useState } from "react";
export function AddProductForm() {
const [name, setName] = useState("");
const [price, setPrice] = useState("");
const { mutate, isPending, isSuccess } = useCreateProduct();
const handleSubmit = () => {
mutate({ name, price: Number(price) });
};
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="পণ্যের নাম"
/>
<input
value={price}
onChange={(e) => setPrice(e.target.value)}
placeholder="দাম"
/>
<button onClick={handleSubmit} disabled={isPending}>
{isPending ? "তৈরি হচ্ছে..." : "পণ্য যোগ করুন"}
</button>
{isSuccess && <p>✅ সফলভাবে তৈরি হয়েছে!</p>}
</div>
);
}
Part 2 — GraphQL
React Query-র fetching mechanism Promise-এর উপর নির্মিত, তাই GraphQL সহ যেকোনো async data fetching client-এর সাথে এটি ব্যবহার করা যায়।
npm install graphql-request graphql
// lib/graphql-client.ts
import { GraphQLClient } from "graphql-request";
export const gqlClient = new GraphQLClient(
process.env.NEXT_PUBLIC_GRAPHQL_URL || "https://api.example.com/graphql",
{
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
},
},
);
// hooks/usePostsGraphQL.ts
import { useQuery } from "@tanstack/react-query";
import { gql } from "graphql-request";
import { gqlClient } from "@/lib/graphql-client";
const GET_POSTS = gql`
query GetPosts($limit: Int!) {
posts(limit: $limit) {
id
title
content
author {
name
avatar
}
createdAt
}
}
`;
type Post = {
id: string;
title: string;
content: string;
author: { name: string; avatar: string };
createdAt: string;
};
export function usePosts(limit = 10) {
return useQuery({
queryKey: ["posts", { limit }], // variables queryKey-এ রাখুন
queryFn: async () => {
const data = await gqlClient.request<{ posts: Post[] }>(GET_POSTS, {
limit,
});
return data.posts;
},
});
}
// GraphQL Mutation
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { gql } from "graphql-request";
import { gqlClient } from "@/lib/graphql-client";
const CREATE_POST = gql`
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
}
}
`;
export function useCreatePost() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (variables: { title: string; content: string }) =>
gqlClient.request(CREATE_POST, variables),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["posts"] });
},
});
}
Part 3 — Next.js-এ সরাসরি DB Call
Next.js App Router-এ Server Action ব্যবহার করে TanStack Query দিয়ে সরাসরি database query করা যায় — কোনো API route ছাড়াই।
// app/actions/product-actions.ts
"use server";
import { db } from "@/lib/db"; // Prisma / Drizzle / যেকোনো ORM
export async function getProducts() {
return await db.product.findMany({
orderBy: { createdAt: "desc" },
});
}
export async function getProductById(id: string) {
return await db.product.findUnique({ where: { id } });
}
export async function createProduct(data: { name: string; price: number }) {
return await db.product.create({ data });
}
export async function deleteProduct(id: string) {
return await db.product.delete({ where: { id } });
}
// hooks/useProductsDB.ts — Server Action + TanStack Query
"use client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
getProducts,
createProduct,
deleteProduct,
} from "@/app/actions/product-actions";
// Server Action সরাসরি queryFn হিসেবে
export function useProductsDB() {
return useQuery({
queryKey: ["products-db"],
queryFn: getProducts, // Server Action এখানে queryFn!
});
}
export function useCreateProductDB() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createProduct, // Server Action mutation হিসেবে
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["products-db"] });
},
});
}
export function useDeleteProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: deleteProduct,
// Optimistic Update — আগেই UI থেকে সরিয়ে দাও
onMutate: async (id) => {
await queryClient.cancelQueries({ queryKey: ["products-db"] });
const previous = queryClient.getQueryData(["products-db"]);
queryClient.setQueryData(["products-db"], (old: any[]) =>
old.filter((p) => p.id !== id),
);
return { previous };
},
// কোনো সমস্যা হলে আগের অবস্থায় ফিরে যাও
onError: (err, id, context) => {
queryClient.setQueryData(["products-db"], context?.previous);
},
});
}
Part 4 — Non-API Use Cases
এখানেই TanStack Query সত্যিকারের শক্তি দেখায়। এটি কোনো API-র সাথে সম্পর্কিত নয় — যেকোনো async কাজের জন্য ব্যবহার করা যায়।
১. LocalStorage থেকে Data পড়া
export function useUserPreferences() {
return useQuery({
queryKey: ["user-preferences"],
queryFn: () => {
const raw = localStorage.getItem("preferences");
return raw ? JSON.parse(raw) : { theme: "light", language: "bn" };
},
staleTime: Infinity, // localStorage কখনো stale না
});
}
export function useSavePreferences() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (prefs: object) => {
localStorage.setItem("preferences", JSON.stringify(prefs));
return prefs;
},
onSuccess: (data) => {
queryClient.setQueryData(["user-preferences"], data);
},
});
}
২. IndexedDB — Offline-First App
import { openDB } from "idb"; // npm install idb
const getDB = () =>
openDB("myapp", 1, {
upgrade(db) {
db.createObjectStore("drafts", { keyPath: "id" });
},
});
export function useDrafts() {
return useQuery({
queryKey: ["drafts"],
queryFn: async () => {
const db = await getDB();
return db.getAll("drafts");
},
});
}
export function useSaveDraft() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (draft: { id: string; content: string }) => {
const db = await getDB();
await db.put("drafts", draft);
return draft;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["drafts"] });
},
});
}
৩. File Upload — Progress সহ
export function useUploadFile() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (file: File) => {
const formData = new FormData();
formData.append("file", file);
const res = await fetch("/api/upload", {
method: "POST",
body: formData,
});
if (!res.ok) throw new Error("আপলোড ব্যর্থ হয়েছে");
return res.json();
},
onSuccess: (uploadedFile) => {
// upload শেষে file list আপডেট করো
queryClient.invalidateQueries({ queryKey: ["user-files"] });
},
});
}
// Component-এ ব্যবহার
function UploadButton() {
const { mutate, isPending, isSuccess, isError } = useUploadFile();
return (
<div>
<input
type="file"
onChange={(e) => {
if (e.target.files?.[0]) mutate(e.target.files[0]);
}}
disabled={isPending}
/>
{isPending && <p>আপলোড হচ্ছে...</p>}
{isSuccess && <p>✅ আপলোড সফল!</p>}
{isError && <p>❌ আপলোড ব্যর্থ হয়েছে</p>}
</div>
);
}
৪. Polling — প্রতি N সেকেন্ডে Auto Refresh
// যেমন: order status, job progress, live score
export function useOrderStatus(orderId: string) {
return useQuery({
queryKey: ["order-status", orderId],
queryFn: () => fetch(`/api/orders/${orderId}/status`).then((r) => r.json()),
refetchInterval: (query) => {
// status "completed" হলে polling বন্ধ
if (query.state.data?.status === "completed") return false;
return 3000; // প্রতি ৩ সেকেন্ডে check
},
});
}
৫. Dependent Queries — একটির উপর নির্ভর করে আরেকটি
// প্রথমে user fetch করো, তারপর সেই user-এর orders
export function useUserWithOrders(userId: string) {
const userQuery = useQuery({
queryKey: ["user", userId],
queryFn: () => fetch(`/api/users/${userId}`).then((r) => r.json()),
});
const ordersQuery = useQuery({
queryKey: ["orders", userId],
queryFn: () => fetch(`/api/orders?userId=${userId}`).then((r) => r.json()),
enabled: !!userQuery.data, // user না পাওয়া পর্যন্ত শুরু হবে না
});
return { user: userQuery, orders: ordersQuery };
}
৬. Parallel Queries — একসাথে অনেক কিছু Fetch
import { useQueries } from "@tanstack/react-query";
export function useDashboard() {
const results = useQueries({
queries: [
{
queryKey: ["stats"],
queryFn: () => fetch("/api/stats").then((r) => r.json()),
},
{
queryKey: ["recent-orders"],
queryFn: () => fetch("/api/orders/recent").then((r) => r.json()),
},
{
queryKey: ["notifications"],
queryFn: () => fetch("/api/notifications").then((r) => r.json()),
},
],
});
return {
stats: results[0],
orders: results[1],
notifications: results[2],
isLoading: results.some((r) => r.isPending),
};
}
৭. Infinite Scroll / Load More
import { useInfiniteQuery } from "@tanstack/react-query";
export function useInfinitePosts() {
return useInfiniteQuery({
queryKey: ["infinite-posts"],
queryFn: ({ pageParam }) =>
fetch(`/api/posts?page=${pageParam}&limit=10`).then((r) => r.json()),
initialPageParam: 1,
getNextPageParam: (lastPage, allPages) => {
// শেষ পেজে পৌঁছালে undefined return করো
return lastPage.hasMore ? allPages.length + 1 : undefined;
},
});
}
// Component
function PostFeed() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfinitePosts();
return (
<div>
{data?.pages.map((page) =>
page.posts.map((post) => <PostCard key={post.id} post={post} />),
)}
{hasNextPage && (
<button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
{isFetchingNextPage ? "লোড হচ্ছে..." : "আরও দেখুন"}
</button>
)}
</div>
);
}
Part 5 — Next.js SSR এবং Prefetching
Server-এ data prefetch করে client-এ hydrate করা — এতে page load-এ blank screen দেখায় না।
// app/products/page.tsx (Server Component)
import {
HydrationBoundary,
QueryClient,
dehydrate,
} from "@tanstack/react-query";
import { getProducts } from "@/app/actions/product-actions";
import { ProductList } from "@/components/ProductList";
export default async function ProductsPage() {
const queryClient = new QueryClient();
// Server-এ আগেই fetch করো
await queryClient.prefetchQuery({
queryKey: ["products"],
queryFn: getProducts,
});
return (
// dehydrated state client-এ পাঠাও
<HydrationBoundary state={dehydrate(queryClient)}>
<ProductList />
</HydrationBoundary>
);
}
// components/ProductList.tsx (Client Component)
"use client";
import { useQuery } from "@tanstack/react-query";
import { getProducts } from "@/app/actions/product-actions";
export function ProductList() {
// Server-এ prefetch হওয়া data সাথে সাথে পাবে — loading দেখাবে না
const { data } = useQuery({
queryKey: ["products"],
queryFn: getProducts,
});
return (
<div>
{data?.map((product) => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}
গুরুত্বপূর্ণ Concepts একনজরে
staleTime vs gcTime
useQuery({
queryKey: ["products"],
queryFn: fetchProducts,
staleTime: 5 * 60 * 1000, // ৫ মিনিট পর্যন্ত "fresh" — নতুন fetch হবে না
gcTime: 10 * 60 * 1000, // ১০ মিনিট পর cache থেকে মুছে যাবে
});
Query Invalidation — Cache রিফ্রেশ
const queryClient = useQueryClient();
// নির্দিষ্ট query invalidate
queryClient.invalidateQueries({ queryKey: ["products"] });
// prefix দিয়ে সব related query invalidate
queryClient.invalidateQueries({ queryKey: ["products"] }); // ["products", 1], ["products", 2] সবই
// সরাসরি cache update (refetch ছাড়া)
queryClient.setQueryData(["products", id], updatedProduct);
queryOptions — Reusable Query Definition
queryOptions function দিয়ে type-safe উপায়ে query definition share করা যায় useQuery এবং queryClient.prefetchQuery-এর মতো imperative method-এর মধ্যে।
// lib/query-options.ts
import { queryOptions } from "@tanstack/react-query";
import { getProducts, getProductById } from "@/app/actions/product-actions";
export const productQueries = {
all: () =>
queryOptions({
queryKey: ["products"],
queryFn: getProducts,
staleTime: 5 * 60 * 1000,
}),
detail: (id: string) =>
queryOptions({
queryKey: ["products", id],
queryFn: () => getProductById(id),
enabled: !!id,
}),
};
// ব্যবহার — সব জায়গায় একই definition
const { data } = useQuery(productQueries.all());
await queryClient.prefetchQuery(productQueries.all());
select — Cache থেকে শুধু দরকারি অংশ নেওয়া
select option দিয়ে cache-এ পুরো data রেখে component-এ শুধু দরকারি অংশ নেওয়া যায়। API বারবার call হয় না, শুধু transform হয়।
// সব product cache-এ আছে, কিন্তু component পাচ্ছে শুধু দামি পণ্যগুলো
const { data: expensiveProducts } = useQuery({
queryKey: ["products"],
queryFn: fetchProducts,
select: (data) => data.filter((p) => p.price > 1000),
});
// অথবা shape বদলে নেওয়া
const { data: productMap } = useQuery({
queryKey: ["products"],
queryFn: fetchProducts,
select: (data) => Object.fromEntries(data.map((p) => [p.id, p])),
});
select-এর সুবিধা হলো — অন্য component একই queryKey দিয়ে পুরো list পাচ্ছে, এই component শুধু filtered অংশ পাচ্ছে, কিন্তু fetch একবারই হচ্ছে।
placeholderData — Smooth Pagination
Page বদলালে সাথে সাথে loading spinner না দেখিয়ে আগের পেজের data রেখে দেওয়া যায়।
import { keepPreviousData, useQuery } from "@tanstack/react-query";
export function useProductsPage(page: number) {
return useQuery({
queryKey: ["products", { page }],
queryFn: () => fetchProductsPage(page),
placeholderData: keepPreviousData, // নতুন data না আসা পর্যন্ত আগেরটা দেখাও
});
}
// Component
function ProductsPage() {
const [page, setPage] = useState(1);
const { data, isFetching, isPlaceholderData } = useProductsPage(page);
return (
<div>
{/* data সবসময় আছে — কোনো blank screen নেই */}
{data?.products.map((p) => (
<ProductCard key={p.id} product={p} />
))}
<div>
<button onClick={() => setPage((p) => p - 1)} disabled={page === 1}>
আগে
</button>
<span> পেজ {page} </span>
<button
onClick={() => setPage((p) => p + 1)}
// পরের পেজ load হওয়ার আগে button disable
disabled={isPlaceholderData || !data?.hasNextPage}
>
পরে
</button>
{isFetching && <span> লোড হচ্ছে...</span>}
</div>
</div>
);
}
useSuspenseQuery — Suspense-র সাথে
useSuspenseQuery ব্যবহার করলে component-এ isPending check করতে হয় না — React Suspense সেটা handle করে। Data সবসময় defined থাকে।
import { useSuspenseQuery } from "@tanstack/react-query";
// isPending নেই — data সবসময় typed ও defined
function ProductList() {
const { data } = useSuspenseQuery({
queryKey: ["products"],
queryFn: fetchProducts,
});
return (
<div>
{data.map((product) => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}
// Parent component-এ Suspense boundary দাও
function ProductsPage() {
return (
<Suspense fallback={<ProductSkeleton />}>
<ProductList />
</Suspense>
);
}
Error handling-এর জন্য ErrorBoundary যোগ করুন:
import { ErrorBoundary } from "react-error-boundary";
function ProductsPage() {
return (
<ErrorBoundary fallback={<p>কিছু একটা সমস্যা হয়েছে।</p>}>
<Suspense fallback={<ProductSkeleton />}>
<ProductList />
</Suspense>
</ErrorBoundary>
);
}
Prefetch on Hover — ইউজার Click করার আগেই Data আনা
Link-এ hover করলেই সেই পেজের data prefetch শুরু করুন — click করলে instant load।
"use client";
import { useQueryClient } from "@tanstack/react-query";
import Link from "next/link";
import { productQueries } from "@/lib/query-options";
function ProductCard({ product }: { product: Product }) {
const queryClient = useQueryClient();
return (
<Link
href={`/products/${product.id}`}
onMouseEnter={() => {
// hover করলেই detail page-এর data আনা শুরু
queryClient.prefetchQuery(productQueries.detail(product.id));
}}
>
{product.name}
</Link>
);
}
useEffect বনাম TanStack Query
// ❌ useEffect দিয়ে — verbose, error-prone, caching নেই
function Products() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch("/api/products")
.then((r) => r.json())
.then((data) => {
if (!cancelled) {
setData(data);
setLoading(false);
}
})
.catch((err) => {
if (!cancelled) {
setError(err);
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, []);
// ...
}
// ✅ TanStack Query দিয়ে — সংক্ষিপ্ত, caching, retry, refetch সব আছে
function Products() {
const { data, isPending, isError } = useQuery({
queryKey: ["products"],
queryFn: () => fetch("/api/products").then((r) => r.json()),
});
// ...
}
উপসংহার
TanStack Query মূলত একটি async state manager। এটি ধরে নেয় না আপনি কোথা থেকে data আনছেন — REST API, GraphQL, DB, localStorage, ফাইল সিস্টেম, যাই হোক না কেন। আপনার application আরও maintainable হয়, নতুন feature যোগ করা সহজ হয়, end-user-এর কাছে application আরও দ্রুত ও responsive মনে হয়, এবং bandwidth ও memory usage কমে।
মনে রাখার নিয়ম সহজ — যদি কিছু async হয়, TanStack Query সেটা manage করতে পারে। REST API হোক, GraphQL হোক, DB call হোক, অথবা শুধু একটি setTimeout — সবখানে এটি একই পরিচিত useQuery / useMutation pattern দিয়ে কাজ করে। এই consistency-ই TanStack Query-কে এত শক্তিশালী করে তোলে।