React Query vs useEffect: Stop Misusing Them in 2026 (They’re Not the Same Thing)
Many influencers say “Just use React Query instead of useEffect” — but that’s misleading. Here’s when to use each one properly.
If you’re a React developer, you’ve probably seen this advice on Twitter, YouTube, or TikTok: “Don’t use useEffect for data fetching — just use React Query.”
This sounds simple… until you realize it’s half-true and often misleads new developers.
React Query (now called TanStack Query) is NOT a direct replacement for useEffect. They solve different problems, even though they sometimes overlap in data fetching.
Let’s clear the confusion once and for all.
What useEffect Actually Does
useEffect is a general-purpose side-effect hook. It runs after render and is perfect for anything that needs to happen outside React’s normal flow.
Common use cases:
- Fetching data (manual way)
- Adding event listeners (resize, scroll, keydown)
- Timers (setTimeout / setInterval)
- Manual DOM manipulations
- Syncing with localStorage, WebSockets, or third-party libraries
- Running analytics or logging on state change
It gives you full control — but you have to handle everything yourself.
What React Query (TanStack Query) Actually Does
React Query is a powerful server-state management library. It’s specifically built for handling data that comes from a server (API calls).
Get Vaidikthakkar’s stories in your inbox
Join Medium for free to get updates from this writer.
It automatically gives you:
- Caching
- Background refetching
- Automatic retries on failure
- Loading & error states
- Deduplication (no duplicate requests)
- Data synchronization across components
- Easy mutations with automatic cache updates
The Key Difference
- useEffect = Low-level tool → You build the entire data-fetching logic yourself.
- React Query = High-level tool → It handles all the complex server-state logic for you.
React Query replaces only one common use case of useEffect: data fetching from a server. It does not replace useEffect entirely.
When You Should Use React Query (Instead of useEffect)
Use React Query when:
- You’re fetching, updating, or syncing server data (GET, POST, PUT, DELETE)
- You have multiple components that need the same data
- You want caching, background updates, or retries
- You’re building a medium-to-large production app
Example with useEffect + useState (Manual Approach):
import { useState } from 'react';
function AddPost() {
const [title, setTitle] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
setLoading(true);
try {
await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({ title }),
headers: { 'Content-Type': 'application/json' },
});
alert('Post added successfully!');
setTitle('');
} catch (err) {
console.error('Failed to add post:', err);
} finally {
setLoading(false);
}
};
return (
<div>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Post title"
/>
<button onClick={handleSubmit} disabled={loading}>
{loading ? 'Adding Post...' : 'Add Post'}
</button>
</div>
);
}
Problems with this approach:
- Manual loading state management
- No automatic cache invalidation
- You must manually refetch the posts list after success
- Weak error handling
- No retry logic
- Code becomes messy as your app grows
2. Using React Query — useMutation (Recommended)
import { useMutation, useQueryClient } from '@tanstack/react-query';
function AddPost() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (newPost) => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify(newPost),
headers: { 'Content-Type': 'application/json' },
});
if (!res.ok) throw new Error('Failed to create post');
return res.json();
},
onSuccess: () => {
// 🔥 Automatically refresh the posts list
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
return (
<div>
<button
onClick={() => mutation.mutate({ title: 'New Awesome Post' })}
disabled={mutation.isPending}
>
{mutation.isPending ? 'Adding Post...' : 'Add Post'}
</button>
{mutation.isError && <p style={{color: 'red'}}>Error: {mutation.error.message}</p>}
</div>
);
}React Query handles loading states, errors, caching, and more — automatically.
When You Should Still Use useEffect (React Query Cannot Help Here)
You must use useEffect for:
- Browser/DOM events (addEventListener for resize, scroll, etc.)
- Timers (setInterval, setTimeout)
- Syncing with localStorage or browser APIs
- Manual DOM changes (focus, measuring elements)
- Integrating third-party libraries (charts, maps, animations)
- Any side effect that is not related to server data
Real examples where useEffect wins:
- Tracking window resize
- Auto-focusing an input
- Saving theme preference to localStorage
- Setting up a WebSocket connection
- Cleaning up a third-party chart library
Real-World Decision Framework
Ask yourself this simple question:
“Is this side effect talking to a server (API)?”
- Yes → Use React Query (useQuery / useMutation)
- No → Use useEffect
Quick Comparison Table
FeatureuseEffect + useStateTanStack Query (React Query)Primary PurposeGeneral side effectsServer state managementData FetchingManualBuilt-inCachingNo (manual)AutomaticBackground RefetchingNoYesRetry on FailureNoYesLoading / Error StatesManualBuilt-inData Sync Across ComponentsHardEasyEvent Listeners / TimersPerfectNot suitableDOM ManipulationPerfectNot suitableBest ForSmall apps, non-server logicProduction apps with APIs
Final Advice for New Developers
Don’t blindly follow influencers who say “Never use useEffect again.” Learn both tools properly:
- Master useEffect first — it teaches you how React works.
- Then adopt TanStack Query for all server-related data. It will save you hundreds of lines of code and many bugs.
In 2026, the professional standard is clear: Use React Query for server state. Use useEffect for everything else.
Have you been using useEffect for API calls? Or already switched to React Query? Drop your experience in the comments — especially the mistakes you made along the way!






