Modern frontend applications are driven by data. From loading user profiles and paginated content to handling real-time updates, seamless API integration sits at the core of today’s user experiences.
As the React ecosystem has matured, so has the way we approach data fetching. The once common pattern of juggling useState and useEffect is no longer sufficient for modern, scalable applications. As complexity grows, manually managing loading states, error handling, caching, and background updates quickly becomes error-prone and difficult to maintain. To build robust and performant interfaces, developers now need more structured and reliable approaches to managing server data.
Why TanStack Query?
TanStack Query is a powerful data-fetching and caching library for React (and other frameworks) that removes much of the complexity around managing server state. Instead of manually wiring network requests and local state, it gives you a clean, declarative way to work with data from your APIs.
In more technical terms, TanStack Query makes fetching, caching, synchronizing, and updating server state in your web applications a breeze. It handles these concerns automatically and efficiently, ensuring your UI stays in sync with the server while reducing boilerplate and common sources of bugs.
Why TanStack Query Over Traditional Fetching?
The Old Way ❌
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
Problems:
- Repetitive boilerplate
- No caching
- No refetching strategy
- Hard to scale across components
The TanStack Query Way
const { data, isLoading, error } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
});
That’s it.
Setting Up TanStack Query
Install the library:
npm i @tanstack/react-query
Devtools
npm i @tanstack/react-query-devtools
When you begin your React Query journey, you'll want these devtools by your side. They help visualize all the inner workings of React Query and will likely save you hours of debugging if you find yourself in a pinch!
Wrap your app with QueryClientProvider:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient();
export function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
<ReactQueryDevtools />
</QueryClientProvider>
);
}
Fetching Data the Right Way
Let’s fetch a list of users:
hooks/fetchUser.ts
const fetchUsers = async () => {
const res = await fetch("/api/users");
if (!res.ok) throw new Error("Failed to fetch users");
return res.json();
};
const Users = () => {
const { data, isLoading, error } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Something went wrong</p>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
The queryKey uniquely identifies the data being fetched and is used by TanStack Query to cache, share, and refetch that data across your application. The queryFn is the function that performs the actual API request, while TanStack Query handles when it runs and how the result is kept in sync with your UI.
Why TanStack Query Makes Your Code Cleaner
TanStack Query helps reduce the amount of boilerplate you write by handling data fetching, caching, and updates out of the box. It gives you a predictable data flow, keeps your data fresh with automatic background updates, and makes advanced patterns like pagination and mutations easy to implement.
As a result, your components stay declarative and focused on rendering the UI, while TanStack Query takes care of the underlying data plumbing for you.

Top comments (0)