ہماری Performance & Scalability سیریز کا حصہ
مکمل گائیڈ پڑھیںReact Query (TanStack): ڈیٹا لانے والے پیٹرنز
React میں ڈیٹا کی بازیافت کا سطحی رقبہ حیرت انگیز طور پر بڑا ہوتا ہے: لوڈنگ اسٹیٹس، ایرر اسٹیٹس، بیک گراؤنڈ ری فیچنگ، میوٹیشنز کے بعد کیشے کی غلط کاری، پرامید اپ ڈیٹس، لامحدود اسکرول، پری فیچنگ، اور ڈپلیکیشن۔ اس منطق کو ہاتھ سے useEffect + useState کے ساتھ لکھنے سے ریس کے حالات، میموری لیک ہونے اور UI کی متضاد حالتیں پیدا ہوتی ہیں۔ TanStack Query (پہلے React Query) v5 صاف، کمپوز ایبل API کے ساتھ ان سب کو حل کرتا ہے۔
یہ گائیڈ پروڈکشن ری ایکٹ ایپلی کیشنز کے لیے TanStack Query v5 پیٹرنز کا احاطہ کرتی ہے — پیچیدہ تبدیلی کے بہاؤ، پرامید اپ ڈیٹس، لامحدود اسکرول، اور Next.js App Router انٹیگریشن کے ذریعے بنیادی سوالات سے۔ ہر پیٹرن میں TypeScript اور ایرر ہینڈلنگ شامل ہوتی ہے۔
اہم ٹیک ویز
- TanStack Query ایک سرور اسٹیٹ لائبریری ہے — اسے کلائنٹ کے لیے صرف UI اسٹیٹ کے لیے استعمال نہ کریں (اس کے لیے Zustand یا UseState استعمال کریں)
- استفسار کی کلیدیں کیش ایڈریس ہیں - ان تمام متغیرات کو شامل کریں جو استفسار کے نتیجے کو متاثر کرتے ہیں۔
staleTimeکنٹرول کرتا ہے جب بیک گراؤنڈ ری فیچنگ ٹرگر کرتا ہے۔gcTimeکنٹرول کرتا ہے جب غیر استعمال شدہ ڈیٹا کوڑا کرکٹ جمع کیا جاتا ہے۔- تغیرات کو
onSuccessمیںqueryClient.invalidateQueries()کے ذریعے متعلقہ سوالات کو باطل کرنا چاہئے- پرامید اپ ڈیٹس سمجھی جانے والی کارکردگی کو بہتر بناتی ہیں لیکن
onErrorمیں رول بیک منطق کی ضرورت ہوتی ہے۔- صفحہ بندی کی فہرستوں کے لیے
useInfiniteQueryاستعمال کریں؛ کبھی بھیuseQuery+ دستی صفحہ کی حالت کو یکجا نہ کریں۔- فوری نیویگیشن کے احساس کے لیے ہوور پر پری فیچ کریں:
queryClient.prefetchQuery()- Next.js ایپ راؤٹر میں، کلائنٹ میں سرور سے حاصل کردہ ڈیٹا کو ڈی ہائیڈریٹ کرنے کے لیے
HydrationBoundaryاستعمال کریں
سیٹ اپ اور کنفیگریشن
pnpm add @tanstack/react-query @tanstack/react-query-devtools
// src/providers/query-provider.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';
export function QueryProvider({ children }: { children: React.ReactNode }) {
// Create QueryClient inside component to avoid shared state between requests (SSR safety)
const [queryClient] = useState(() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute — data is fresh for 1 minute
gcTime: 5 * 60 * 1000, // 5 minutes — keep unused cache for 5 minutes
retry: 2, // Retry failed requests twice
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30_000), // Exponential backoff
refetchOnWindowFocus: true, // Refetch when user returns to tab
refetchOnReconnect: true, // Refetch after network reconnect
},
mutations: {
retry: 0, // Do not retry mutations (non-idempotent)
},
},
})
);
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
// src/app/[locale]/layout.tsx
import { QueryProvider } from '@/providers/query-provider';
export default function LocaleLayout({ children }: { children: React.ReactNode }) {
return (
<QueryProvider>
{children}
</QueryProvider>
);
}
سوال کی چابیاں - فاؤنڈیشن
استفسار کی کلیدیں آپ کے ڈیٹا کے لیے کیشے کا پتہ ہیں۔ ہر متغیر جو استفسار کے نتیجے کو متاثر کرتا ہے کلید میں ہونا چاہیے۔
// src/lib/query-keys.ts
// Centralize query keys to avoid typos and enable easy invalidation
export const queryKeys = {
contacts: {
all: () => ['contacts'] as const,
lists: () => [...queryKeys.contacts.all(), 'list'] as const,
list: (filters: ContactFilters) =>
[...queryKeys.contacts.lists(), filters] as const,
details: () => [...queryKeys.contacts.all(), 'detail'] as const,
detail: (id: string) =>
[...queryKeys.contacts.details(), id] as const,
},
orders: {
all: () => ['orders'] as const,
list: (orgId: string, page: number) => ['orders', orgId, page] as const,
detail: (id: string) => ['orders', id] as const,
},
} as const;
// Usage in queries and invalidations:
// useQuery({ queryKey: queryKeys.contacts.list({ page: 1, limit: 20 }) })
// queryClient.invalidateQueries({ queryKey: queryKeys.contacts.lists() })
// — invalidates ALL list queries regardless of filter parameters
بنیادی سوالات
// src/hooks/use-contacts.ts
import { useQuery } from '@tanstack/react-query';
import { apiFetch } from '@/lib/api';
import { queryKeys } from '@/lib/query-keys';
interface ContactFilters {
page: number;
limit: number;
search?: string;
status?: string;
}
interface ContactsResponse {
data: Contact[];
total: number;
page: number;
limit: number;
}
export function useContacts(filters: ContactFilters) {
return useQuery({
queryKey: queryKeys.contacts.list(filters),
queryFn: async () => {
const params = new URLSearchParams({
page: String(filters.page),
limit: String(filters.limit),
...(filters.search && { search: filters.search }),
...(filters.status && { status: filters.status }),
});
return apiFetch<ContactsResponse>(`/contacts?${params}`);
},
placeholderData: (previousData) => previousData, // Keep previous page data while fetching
staleTime: 30_000, // Contacts data: 30 second stale time
});
}
export function useContact(id: string) {
return useQuery({
queryKey: queryKeys.contacts.detail(id),
queryFn: () => apiFetch<Contact>(`/contacts/${id}`),
enabled: !!id, // Only fetch if id is provided
});
}
// Usage in a component
'use client';
export default function ContactsPage() {
const [page, setPage] = useState(1);
const [search, setSearch] = useState('');
const { data, isLoading, isError, error, isFetching } = useContacts({
page,
limit: 20,
search: search || undefined,
});
if (isLoading) return <ContactsSkeleton />;
if (isError) return <ErrorState message={error.message} />;
return (
<div>
{isFetching && <RefetchIndicator />} {/* Shows during background refetch */}
<ContactsTable data={data.data} />
<Pagination
page={page}
total={data.total}
limit={20}
onChange={setPage}
/>
</div>
);
}
کیشے کی غلط کاری کے ساتھ تغیرات
// src/hooks/use-contact-mutations.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiFetch } from '@/lib/api';
import { queryKeys } from '@/lib/query-keys';
import { toast } from '@/components/ui/toast';
export function useCreateContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateContactDto) =>
apiFetch<Contact>('/contacts', {
method: 'POST',
body: JSON.stringify(data),
}),
onSuccess: (newContact) => {
// Invalidate all contact list queries — they are now stale
queryClient.invalidateQueries({
queryKey: queryKeys.contacts.lists(),
});
// Seed the detail cache immediately — no need to refetch
queryClient.setQueryData(
queryKeys.contacts.detail(newContact.id),
newContact
);
toast({ title: 'Contact created', variant: 'success' });
},
onError: (error: Error) => {
toast({ title: 'Failed to create contact', description: error.message, variant: 'destructive' });
},
});
}
export function useDeleteContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) =>
apiFetch(`/contacts/${id}`, { method: 'DELETE' }),
onSuccess: (_data, deletedId) => {
// Remove from all list queries
queryClient.invalidateQueries({ queryKey: queryKeys.contacts.lists() });
// Remove detail cache
queryClient.removeQueries({ queryKey: queryKeys.contacts.detail(deletedId) });
toast({ title: 'Contact deleted', variant: 'success' });
},
});
}
پر امید اپڈیٹس
پرامید اپ ڈیٹس سرور کی تصدیق کرنے سے پہلے UI کو اپ ڈیٹ کر کے تغیرات کو فوری محسوس کرتے ہیں:
export function useUpdateContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateContactDto }) =>
apiFetch<Contact>(`/contacts/${id}`, {
method: 'PATCH',
body: JSON.stringify(data),
}),
// Called immediately before mutationFn
onMutate: async ({ id, data }) => {
// Cancel any in-flight refetches for this contact
await queryClient.cancelQueries({ queryKey: queryKeys.contacts.detail(id) });
// Snapshot the previous value for rollback
const previousContact = queryClient.getQueryData<Contact>(
queryKeys.contacts.detail(id)
);
// Optimistically update the UI
queryClient.setQueryData<Contact>(
queryKeys.contacts.detail(id),
(old) => old ? { ...old, ...data } : old
);
return { previousContact }; // Passed to onError context
},
onError: (_error, { id }, context) => {
// Roll back to the snapshot on error
if (context?.previousContact) {
queryClient.setQueryData(
queryKeys.contacts.detail(id),
context.previousContact
);
}
toast({ title: 'Update failed', variant: 'destructive' });
},
onSettled: (_data, _error, { id }) => {
// Always refetch after success or error to sync with server truth
queryClient.invalidateQueries({ queryKey: queryKeys.contacts.detail(id) });
},
});
}
لامحدود اسکرول / صفحہ بندی
// src/hooks/use-infinite-contacts.ts
import { useInfiniteQuery } from '@tanstack/react-query';
export function useInfiniteContacts(search?: string) {
return useInfiniteQuery({
queryKey: ['contacts', 'infinite', search],
queryFn: ({ pageParam = 1 }) =>
apiFetch<ContactsResponse>(`/contacts?page=${pageParam}&limit=20${search ? `&search=${search}` : ''}`),
initialPageParam: 1,
getNextPageParam: (lastPage) => {
const nextPage = lastPage.page + 1;
const maxPage = Math.ceil(lastPage.total / lastPage.limit);
return nextPage <= maxPage ? nextPage : undefined;
},
staleTime: 30_000,
});
}
// Infinite scroll component
'use client';
import { useInfiniteContacts } from '@/hooks/use-infinite-contacts';
import { useInView } from 'react-intersection-observer';
import { useEffect } from 'react';
export function InfiniteContactsList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteContacts();
const { ref, inView } = useInView({ threshold: 0.5 });
// Fetch next page when the sentinel element enters the viewport
useEffect(() => {
if (inView && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
if (isLoading) return <ContactsSkeleton />;
const contacts = data?.pages.flatMap((page) => page.data) ?? [];
return (
<div>
{contacts.map((contact) => (
<ContactCard key={contact.id} contact={contact} />
))}
{/* Sentinel element — triggers next page load when visible */}
<div ref={ref} className="h-4">
{isFetchingNextPage && <LoadingSpinner />}
</div>
</div>
);
}
فوری نیویگیشن کے لیے پیشگی بازیافت
// Prefetch on hover — data is ready before the user clicks
'use client';
import { useQueryClient } from '@tanstack/react-query';
import { Link } from '@/i18n/navigation';
import { queryKeys } from '@/lib/query-keys';
import { apiFetch } from '@/lib/api';
export function ContactRow({ contact }: { contact: Contact }) {
const queryClient = useQueryClient();
const handleMouseEnter = () => {
queryClient.prefetchQuery({
queryKey: queryKeys.contacts.detail(contact.id),
queryFn: () => apiFetch<Contact>(`/contacts/${contact.id}`),
staleTime: 60_000, // Use cached if less than 1 minute old
});
};
return (
<tr onMouseEnter={handleMouseEnter}>
<td>
<Link href={`/dashboard/contacts/${contact.id}`}>{contact.name}</Link>
</td>
<td>{contact.email}</td>
</tr>
);
}
Next.js ایپ راؤٹر SSR + TanStack استفسار
// src/app/[locale]/dashboard/contacts/page.tsx (Server Component)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
import { queryKeys } from '@/lib/query-keys';
import { getServerContacts } from '@/lib/server-api';
import { ContactsClient } from './contacts-client';
export default async function ContactsPage({ params }: Props) {
const { locale } = await params;
const queryClient = new QueryClient();
// Prefetch on the server — data is embedded in the HTML
await queryClient.prefetchQuery({
queryKey: queryKeys.contacts.list({ page: 1, limit: 20 }),
queryFn: () => getServerContacts({ page: 1, limit: 20 }),
});
return (
// HydrationBoundary serializes the server QueryClient state
// into the HTML, which the client QueryClient then picks up
<HydrationBoundary state={dehydrate(queryClient)}>
<ContactsClient />
</HydrationBoundary>
);
}
// src/app/[locale]/dashboard/contacts/contacts-client.tsx ('use client')
'use client';
import { useContacts } from '@/hooks/use-contacts';
export function ContactsClient() {
const { data } = useContacts({ page: 1, limit: 20 });
// data is immediately available from the server-dehydrated state
// No loading state on first render — SSR data is used instantly
return <ContactsTable data={data?.data ?? []} />;
}
اکثر پوچھے گئے سوالات
مجھے TanStack Query بمقابلہ SWR بمقابلہ سادہ بازیافت کب استعمال کرنا چاہئے؟
جب آپ کو ضرورت ہو تو TanStack استفسار کا استعمال کریں: اتپریورتنوں کے بعد کیشے کی غلط کاری، پرامید اپ ڈیٹس، لامحدود اسکرول، پری فیچنگ، بیک گراؤنڈ ری فیچنگ، یا ڈپلیکیشن کی درخواست کریں۔ اگر آپ کو چھوٹے بنڈل کی ضرورت ہو اور صرف سادہ استعمال کے معاملات کی ضرورت ہو تو SWR استعمال کریں۔ Next.js سرور کے اجزاء میں سادہ fetch استعمال کریں جہاں آپ React کا مقامی ڈیٹا کیشنگ چاہتے ہیں (cache() فنکشن اور revalidatePath() پیٹرن)۔ TanStack Query کلائنٹ سائیڈ انٹرایکٹیویٹی کے لیے ہے جہاں صارف کے اعمال سرور کے ڈیٹا کو متاثر کرتے ہیں۔
staleTime اور gcTime میں کیا فرق ہے؟
staleTime یہ ہے کہ کتنے عرصے تک ڈیٹا کو تازہ سمجھا جاتا ہے — اس وقت کے دوران، کوئی بیک گراؤنڈ ری فیچ نہیں ہوتا ہے اور کیشڈ ڈیٹا فوری طور پر واپس کر دیا جاتا ہے۔ staleTime کی میعاد ختم ہونے کے بعد، ڈیٹا "باسی" ہے اور اگلے استعمال پر پس منظر میں دوبارہ حاصل کیا جائے گا۔ gcTime (پہلے cacheTime) یہ ہے کہ کب تک غیر استعمال شدہ (کوئی فعال سبسکرائبر نہیں) ڈیٹا کوڑا کرکٹ جمع کرنے سے پہلے میموری میں رہتا ہے۔ staleTime اس بنیاد پر سیٹ کریں کہ آپ کا ڈیٹا کتنی تیزی سے تبدیل ہوتا ہے۔ gcTime اس بنیاد پر سیٹ کریں کہ آپ کتنی دیر تک فوری طور پر محسوس کرنے کے لیے دور اور واپس جانا چاہتے ہیں۔
میں عالمی سطح پر تصدیق کی غلطیوں (401) کو کیسے ہینڈل کروں؟
اپنے QueryClient ڈیفالٹ اختیارات میں ایک عالمی ایرر ہینڈلر شامل کریں۔ 401 پر، لاگ ان کرنے کے لیے ری ڈائریکٹ کریں اور استفسار کیش کو صاف کریں۔ TanStack Query v5 میں: defaultOptions: { queries: { throwOnError: (error) => { if (error.status === 401) { router.push('/auth/login'); queryClient.clear(); return false; } return true; } } }۔ متبادل طور پر، logout() فنکشن کو کال کرکے اپنے apiFetch مددگار میں 401 کو ہینڈل کریں۔
میں پروپ ڈرلنگ کے بغیر بہن بھائیوں کے درمیان استفسار کی حالت کا اشتراک کیسے کروں؟
کوئی بھی جزو جو ایک ہی سوال کلید کے ساتھ useQuery کو کال کرتا ہے وہی کیشڈ ڈیٹا شیئر کرتا ہے — TanStack Query درخواستوں کو خود بخود ڈپلیکیٹ کرتا ہے۔ دو ContactsTable اجزاء اور ایک ContactCount بیج کالنگ useContacts({ page: 1, limit: 20 }) ایک ہی فلٹرز کے ساتھ سبھی ایک ہی کیش شدہ درخواست سے پڑھیں گے۔ کوئی سہارا ڈرلنگ، کوئی سیاق و سباق کی ضرورت نہیں.
کیا استفسار کی کلیدوں میں صارف کا سیشن یا تنظیم کا ID شامل ہونا چاہیے؟
ہاں — اگر ڈیٹا کا دائرہ کسی صارف یا تنظیم کے لیے ہے، تو استفسار کلید میں اسکوپنگ شناخت کنندہ کو شامل کریں۔ یہ ایک صارف کا ڈیٹا دوسرے کے لیے ظاہر ہونے سے روکتا ہے (ملٹی کرایہ دار ایپس کے لیے اہم)۔ لاگ آؤٹ پر پوری استفسار کیش کو بھی صاف کریں: queryClient.clear() یا queryClient.removeQueries() — بصورت دیگر پچھلے سیشن کا باسی ڈیٹا اگلے صارف کے لیے مختصر طور پر چمک سکتا ہے۔
اگلے اقدامات
TanStack Query ریاستی انتظامی مسئلے سے React ڈیٹا کی بازیافت کو اعلانیہ، کیشے سے آگاہی والے نظام میں تبدیل کرتا ہے۔ پرامید اپ ڈیٹس، پری فیچنگ، اور SSR ہائیڈریشن آپ کے صارفین کو ایک ایسا تجربہ فراہم کرتے ہیں جو فوری محسوس ہوتا ہے — یہاں تک کہ سست روابط پر بھی۔
ECOSIRE تمام سرور اسٹیٹ کے لیے TanStack Query کے ساتھ Next.js ایڈمن پینلز اور کسٹمر پورٹلز بناتا ہے، جو صرف کلائنٹ کے لیے UI اسٹیٹ کے لیے Zustand اور ڈیٹا ہیوی ویوز کے لیے TanStack ٹیبل کے ساتھ مل کر بناتا ہے۔ ہماری فرنٹ اینڈ انجینئرنگ سروسز کو دریافت کریں یہ جاننے کے لیے کہ ہم پرفارمنس، پروڈکشن کے لیے تیار ری ایکٹ ایپلی کیشنز کیسے بناتے ہیں۔
تحریر
ECOSIRE Research and Development Team
ECOSIRE میں انٹرپرائز گریڈ ڈیجیٹل مصنوعات بنانا۔ Odoo انٹیگریشنز، ای کامرس آٹومیشن، اور AI سے چلنے والے کاروباری حل پر بصیرت شیئر کرنا۔
متعلقہ مضامین
Next.js 16 App Router: Production Patterns and Pitfalls
Production-ready Next.js 16 App Router patterns: server components, caching strategies, metadata API, error boundaries, and performance pitfalls to avoid.
Nginx Production Configuration: SSL, Caching, and Security
Nginx production configuration guide: SSL termination, HTTP/2, caching headers, security headers, rate limiting, reverse proxy setup, and Cloudflare integration patterns.
Optimizing AI Agent Costs: Token Usage and Caching
Practical strategies for reducing AI agent operational costs through token optimization, caching, model routing, and usage monitoring. Real savings from production OpenClaw deployments.
Performance & Scalability سے مزید
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
Nginx Production Configuration: SSL, Caching, and Security
Nginx production configuration guide: SSL termination, HTTP/2, caching headers, security headers, rate limiting, reverse proxy setup, and Cloudflare integration patterns.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
Odoo vs Acumatica: Cloud ERP for Growing Businesses
Odoo vs Acumatica compared for 2026: unique pricing models, scalability, manufacturing depth, and which cloud ERP fits your growth trajectory.
Testing and Monitoring AI Agents in Production
A complete guide to testing and monitoring AI agents in production environments. Covers evaluation frameworks, observability, drift detection, and incident response for OpenClaw deployments.
Compliance Monitoring Agents with OpenClaw
Deploy OpenClaw AI agents for continuous compliance monitoring. Automate regulatory checks, policy enforcement, audit trail generation, and compliance reporting.