When I first learned React, I thought calling APIs was one of the simplest parts.
Create a few states:
dataloadingerror
Then use useEffect to fetch the data.
Everything worked fine.
At least, until the project started getting bigger.
I remember working on a KPI reporting screen once. Initially, there was only one API to fetch data. After a few rounds of revisions, the screen started to include:
- Search
- Filter
- Pagination
- Data refreshing
- Data synchronization after updates
And that’s when the component started to bloat.
What was supposed to be used solely for displaying data had to handle all sorts of API-related tasks.
After a while, I realized the problem wasn’t React or useEffect.
The problem was how I was managing data from the server.
What is React Query (TanStack Query)?
React Query (now TanStack Query) is a library that helps manage server state in React applications.
Whether you are optimizing a web portal or looking into [Mobile App Development] with frameworks like React Native, robust state management is the key to a smooth user experience.
Instead of writing your own useEffect and useState to handle:
- Loading
- Error
- Caching
- Refetching
- Data synchronization
…we just need to declare:
- Where the data comes from (
queryFn) - What that data is (
queryKey)
React Query handles the rest.
It sounds simple, but this is exactly what makes React code much cleaner.
Why are useEffect + useState no longer enough as the project grows?
Actually, calling an API only takes a few lines of code.
For example:
JavaScript
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers();
}, []);
But the problem doesn’t lie in the first request.
The problem lies in what comes after.
When building a real-world screen, we usually need:
- Loading state
- Error state
- Retry on failed requests
- Data refresh
- Data caching
- Data synchronization after updates
Every API needs similar things.
I didn’t notice it at first.
But when the project grew to include dozens of screens, I realized I was repeating the exact same code pattern in many places.
The only difference was the API name.
Signs that a React component is overloaded with API logic
I always prefer components where I can read the code and immediately understand what they are doing.
For example:
- Fetch data
- Render UI
- Done.
But in reality, many components gradually become a dumping ground for all sorts of logic:
Plaintext
UserPage
├── Fetch API
├── Loading State
├── Error State
├── Retry Logic
├── Refresh Logic
├── Cache Logic
└── UI Rendering
When you need to modify or debug, reading the code becomes quite exhausting.
Especially with:
- Dashboards
- CRMs
- Reporting Systems
- Admin Systems
Places where many APIs are operating simultaneously.
Building these types of large-scale platforms requires solid architecture. If your team is tackling complex data flows, you can read more about our approach to [System Development].
How does React Query solve this problem?
To be honest, I wasn’t very interested in React Query at first.
I used to think: It’s just for calling APIs, do I really need another library?
But after applying it to a real project, I started to see the difference.
The first thing I liked was that the components became shorter.
Instead of manually managing a bunch of data-related states, I just need to declare where the data comes from.
For example:
JavaScript
const { data, isLoading, error } = useQuery({
queryKey: ["users"],
queryFn: getUsers,
});
And then focus on rendering:
JavaScript
if (isLoading) return <Loading />;
if (error) return <Error />;
return <UserTable users={data} />;
You read it, and you instantly understand what the component is doing.
The real benefit of React Query is not caching
When mentioning React Query, most people immediately think of caching.
It’s true that caching is very useful.
Users navigate between screens faster.
The server receives fewer requests.
But for me, that’s not its biggest value.
What I like most is that React Query helps separate the UI from the data management logic.
The component solely focuses on rendering.
Plaintext
React Component
│
▼
TanStack Query
│
┌─────┴─────┐
▼ ▼
Cache API
│
▼
UI Update
Things like:
- Loading
- Retry
- Refetch
- Cache
…are put in their rightful place.
Thanks to this, the code is much easier to read.
React Query vs Redux: Which one to use for server state?
One thing I used to do was put a lot of API data into Redux.
For example:
- Users
- Orders
- Products
- Reports
After a while, the store became massive.
When I dug deeper, I realized that data fetched from the server is a completely different type of state.
It requires:
- Caching
- Synchronization
- Refetching
- Periodic updates
That is exactly the problem React Query was born to solve.
This doesn’t mean React Query replaces Redux.
- Redux is suitable for client state.
- React Query is suitable for server state.
Since adopting React Query, the amount of API data I store in Redux has decreased significantly.
When to use React Query, and when is useEffect enough?
If you are building:
- A landing page
- An introductory website
- A few simple APIs
…then useEffect is perfectly fine.
But if the project starts to include:
- Dashboards
- CRMs
- ERPs
- Reporting Systems
- Admin Portals
…then the benefits of React Query become very clear.
Especially as the number of APIs grows.
Conclusion
After a few years of working with React, I’ve realized that the hardest part isn’t calling APIs.
The hardest part is managing data consistently as the application scales.
TanStack Query doesn’t do anything overly magical.
It simply excellently solves the problems that almost every React project will eventually face.
For me, the greatest value of React Query isn’t caching or performance.
It’s helping the component return to its proper role: rendering the UI.
When I open a React file and see that most of the code is UI rather than data-fetching logic, that’s when I know my codebase is becoming cleaner.
Related reading
- Delivering scalable mobile solutions: why we choose Expo
- Why all tests pass but code still breaks in production

Tho NguyenTho Nguyen
Fullstack Developer · Linnoedge Inc.
Building web applications with modern technologies across frontend and backend. Passionate about scalability, performance, and continuous improvement.