08/05/2025
7 min read
tanstack
state-managment
react

TanStack Query (formerly React Query) is a powerful asynchronous state management library specifically designed for managing server-side data in modern applications. It handles data fetching, caching, synchronization, and updates with minimal boilerplate code, solving many pain points that developers face when working with remote data.
Core capabilities:
Before TanStack Query, developers typically managed server data using one of these approaches:
*// Traditional approach - lots of boilerplate and issues*
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user.name}</div>;
}
Problems with this approach:
useEffect was built for handling side effects (like subscriptions, DOM manipulation), not for managing complex asynchronous data fetching logicloading, error, and data statesuserId changes rapidly, older requests might resolve after newer ones, causing stale data to overwrite fresh data// Redux approach - treating async data as client state
const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
reducers: {
fetchUserStart: (state) => { state.loading = true; },
fetchUserSuccess: (state, action) => {
state.data = action.payload;
state.loading = false;
},
fetchUserFailure: (state, action) => {
state.error = action.payload;
state.loading = false;
}
}
});
*// Thunk for fetching*
const fetchUser = (userId) => async (dispatch) => {
dispatch(fetchUserStart());
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
dispatch(fetchUserSuccess(data));
} catch (error) {
dispatch(fetchUserFailure(error.message));
}
};`
Problems with this approach:
The core issue is that server state and client state are fundamentally different and require different management strategies.
Imagine an e-commerce site showing product inventory
Multiple users are viewing the same product page
User A sees: "10 items in stock"
User B buys 3 items
User C buys 2 items
User A still sees: "10 items in stock" ❌ (outdated!)
Actual inventory: 5 items
Without proper server state management, User A might try to buy
10 items and face an error at checkout*`
This is why server state needs:
*// TanStack Query approach - clean and powerful*
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
staleTime: 5 * 60 * 1000, *// Consider data fresh for 5 minutes*
refetchOnWindowFocus: true, *// Refetch when user returns to tab*
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user.name}</div>;
}
What TanStack Query provides automatically:
TanStack Query exists because server state is fundamentally different from client state and requires specialized tools. Traditional approaches like useState + useEffect or general-purpose state managers like Redux weren't designed for the unique challenges of async server data: remote persistence, shared ownership, asynchronous operations, and the constant risk of data becoming stale.
TanStack Query provides a purpose-built solution that handles caching, synchronization, refetching, and all the complexity of server state management, letting you focus on building features instead of managing infrastructure.
Next js best practices
in next config ts
images: {
remotePatterns: [...],
formats: ["image/avif", "image/webp"],
deviceSizes: [...],
imageSizes: [...],
minimumCacheTTL: 60,
}
remote patterns allow nextjs to optmize external image domains so for exmaple cloudinary image url
inside remote pattern obj
{
protocol: "https",
hostname: "res.cloudinary.com",
pathname: "/sauravjha/**",
}
Only images from https://res.cloudinary.com/sauravjha/... will be optimized.
format ,devicesize , images size will be given basd on different size to optmize bs cache wala smjh lo
minimumCacheTTL → Minimum cache time (in seconds) for optimized images — here, 60 seconds.
compress: true
This enables Gzip/Brotli compression to reduce the size of responses sent to the client.
poweredByHeader: false
Removes the X-Powered-By: Next.js header from responses — a small security and performance best practice.
reactStrictMode: true
Enables React’s Strict Mode in development to help detect side effects, deprecated APIs, and potential bugs.
experimental: {
optimizePackageImports: ["lucide-react", "framer-motion"],
}
This tells Next.js to optimize imports for these packages, so it only bundles what’s actually used.
Example: instead of importing all icons from lucide-react, only the ones you use get bundled — improving performance and bundle size.
sbse jyda hum packages use krte hai toh optmizepackageimport should be there in all
sample next.config.ts for learning
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// Image optimization
images: {
remotePatterns: [
{
protocol: "https",
hostname: "res.cloudinary.com",
pathname: "/sauravjha/**",
},
{
protocol: "https",
hostname: "www.pinecone.io",
pathname: "/images/**",
},
],
formats: ["image/avif", "image/webp"],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60,
},
// Compression
compress: true,
// Production optimizations
poweredByHeader: false,
// React strict mode
reactStrictMode: true,
// Experimental features for better performance
experimental: {
optimizePackageImports: ["lucide-react", "framer-motion"],
},
};
export default nextConfig;