Careeroza
React Query with Next.js
When You Still Fetch on the Client
Most reads should happen in Server Components. Client-side fetching is still useful for data that changes while the page is open: live order status, infinite scroll, polling, autocomplete. TanStack Query (React Query) gives caching, retries and refetching.
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
export default function Providers({ children }: { children: React.ReactNode }) {
const [client] = useState(() => new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } } }));
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
}Polling Order Status
'use client';
import { useQuery } from '@tanstack/react-query';
type Status = { orderId: string; status: 'PLACED' | 'PACKED' | 'SHIPPED' | 'DELIVERED'; eta: string };
export default function OrderStatus({ orderId, initial }: { orderId: string; initial: Status }) {
const { data, isFetching } = useQuery({
queryKey: ['order-status', orderId],
queryFn: async (): Promise<Status> => {
const res = await fetch('/api/orders/' + orderId + '/status');
if (!res.ok) throw new Error('Failed');
return res.json();
},
initialData: initial, // rendered by the Server Component, no loading flash
refetchInterval: (q) => (q.state.data?.status === 'DELIVERED' ? false : 15_000),
});
return (
<p>
{data.status} — ETA {data.eta} {isFetching && '(updating)'}
</p>
);
}Tip: Pass server-fetched data as initialData (or use hydration with HydrationBoundary) so the first paint has real content.
Choosing a Tool
| State | Best place |
|---|---|
| Server data for the page | Server Component (+ cache) |
| Filters, sort, page, tab | URL search params |
| Form input | Local useState / form actions |
| Cart, theme, logged-in user (UI) | Context or Zustand |
| Live / polling / infinite lists | React Query |