Data Fetching in React
16 Jul 2025

Salman Alfarisi
Fullstack Engineer
Introduction
Fetching data is one of the most common things you do in React.
At first, it feels simple. Fetch some data, store it in state, then render it on the page.
But real applications usually need more than that. You start dealing with loading states, failed requests, caching, and refetching data without breaking the UI.
In this post, we will start with the basic useEffect approach, then move to React Query and compare the differences.
Fetching Data with useEffect
Basic Example
The most common way to fetch data in React is with useEffect.
This works fine for smaller projects.
When the component mounts, the request runs and the response gets stored into state.
The Problem
The issue is that this only handles the happy path.
There is no loading state, no error handling, and no feedback for users while the request is happening.
If the API is slow or fails completely, the page just sits there empty.
Adding Loading and Error States
Improving the Experience
A more realistic version usually includes loading and error handling.
This already feels much better for users.
The UI now communicates what is happening instead of silently failing.
Repetitive Logic
The downside is that this pattern gets repetitive quickly.
Almost every fetch request starts needing:
- Loading state
- Error state
- Retry handling
- Refetch logic
After building a few pages, you end up rewriting the same structure repeatedly.
That is one of the main reasons libraries like React Query became popular.
Introducing React Query
Installing React Query
First, install the package:
Then wrap your app with QueryClientProvider.
Fetching with useQuery
Cleaner Fetch Logic
Now we can rewrite the same example using useQuery.
The component becomes noticeably cleaner.
We no longer manage loading or error state manually because React Query handles them for us.
Built-in Features
React Query also gives us features like:
- Caching
- Background refetching
- Retry logic
- Request deduplication
- Devtools
Without React Query, most of these would need to be implemented manually.
Comparing Both Approaches
| Feature | useEffect + fetch | React Query |
|---|---|---|
| Data fetching | ✅ | ✅ |
| Loading state | Manual | Built-in |
| Error handling | Manual | Built-in |
| Caching | ❌ | ✅ |
| Background refetching | ❌ | ✅ |
| Retry logic | ❌ | ✅ |
| Boilerplate | High | Lower |
Final Thoughts
Using useEffect is still completely fine for simpler applications.
But as projects grow, manually managing fetch logic becomes repetitive pretty quickly.
React Query simplifies a lot of that work by handling caching, loading states, retries, and synchronization out of the box.
If you want to explore more advanced features like mutations, pagination, or optimistic updates, the official docs are worth checking out.
React Query Docs: https://tanstack.com/query/latest
Image by Mike Yukhtenko