AppTech Logo
 TanStack Query vs useEffect : Stop Solving Problems You Don't Have
Discover

TanStack Query vs useEffect : Stop Solving Problems You Don't Have

SA
Shahbaz Ahmad
Web Developer
July 03, 2026

Every few years, the React ecosystem introduces a "must-have" library. Redux. MobX. Recoil. Apollo. SWR. TanStack Query.

Every new tool arrives with a promise to simplify development. Yet, paradoxically, many projects end up becoming more complicated than the problems they were originally trying to solve. Developers often adopt these tools without fully understanding the specific friction they are designed to eliminate.

When it comes to data fetching, the real question isn't: "Should I use TanStack Query?"

It is: "What problem am I actually trying to solve?"

This article isn't just a comparison of APIs. It’s a guide to shifting your mental model from blindly adopting tools to understanding the fundamental differences between client-side effects and server state.

React's Actual Job

React's job is simple: it turns state into UI.

Fetching data, caching it, retrying failed requests, and keeping it up to date are not React's responsibility. That's why libraries like TanStack Query exist.

Because React doesn't provide a built-in data-fetching abstraction, developers historically started fetching data inside useEffect. Developers used useEffect because it worked—not because it was the best tool for managing server data.

The "Aha!" Moment: When Fetching Breaks

To understand why we need specialized tools, we first need to visualize how a React application grows over time.

When you start a project, fetching data feels trivial. But as the app scales, the cracks begin to show:

text·Code
11 API Call
23├── useEffect ✅ (Perfectly fine)
455 API Calls
67├── Still manageable
8920 API Calls
1011├── Loading state everywhere
12├── Duplicate requests
13├── Manual refetching
14├── Repeated code
1516100 API Calls
1718└── Time for TanStack Query

At 1 API call, useEffect is all you need. But at 100 API calls, manual fetching becomes a maintenance nightmare. This progression is exactly why server-state libraries were invented.

Why Manual Fetching Gets Messy

Let’s look at what a "proper" data fetch inside a useEffect actually looks like when you account for edge cases:

getusers·TypeScript
1
2useEffect(() => {
3  let cancelled = false;
4  
5  async function load() {
6    try {
7        setLoading(true);
8        const data = await api.getUsers();
9    
10        if (!cancelled) {
11              setUsers(data);
12          }
13      } catch (e) {
14              if (!cancelled) {
15                      setError(e);
16                }
17      } finally {
18            if (!cancelled) {
19                    setLoading(false);
20              }
21      }
22    }
23    
24    load();
25    
26    return () => { cancelled = true; };
27}, []);

A single API call means you now have to manage:

  • Loading
  • Errors
  • Cleanup
  • Duplicate requests

It doesn't seem like much until your app has dozens of API calls. Suddenly, you are copy-pasting this boilerplate across every component, trying to manage race conditions, stale data, and background refreshes manually.

What TanStack Query Actually Solves

TanStack Query doesn't replace fetch().

It manages everything around your API calls—caching, refetching, retries, and keeping data in sync.

Instead of just executing a network request, TanStack Query acts as a dedicated manager for your server data. It handles request deduplication, background updates, and optimistic UI changes out of the box. It isn't just a fetching library; it is a server state management library.

Server State vs. Client State

To truly understand when to use TanStack Query, you need to recognize the two distinct types of state in your application.

There are two types of state in React:

Client state is data your app owns.

  • Theme (light/dark mode)
  • Modal open/close
  • Form inputs
  • Sidebar collapsed

Server state is data that comes from an API.

  • Users
  • Products
  • Orders
  • Notifications

React manages client state well. TanStack Query helps manage server state. Mixing them up—like trying to store UI state in a global server cache—is one of the most common mistakes developers make.

The Mental Model: useQuery vs useMutation

One of the biggest mistakes beginners make is trying to memorize the entire TanStack Query API. Instead, remember one simple rule:

Reading data? → useQuery → GET
Changing data?→ useMutation →POST PUT PATCH DELETE

If you are fetching a list of users, that's a useQuery. If you are submitting a form to create a new user, that's a useMutation. Once you internalize this distinction, the rest of the library becomes incredibly intuitive.

Step-by-Step: From Manual to TanStack Query

Let’s look at how this mental model changes your actual code.

The Manual Approach


Imagine a component that displays a list of users. Without TanStack Query, the component is responsible for the entire lifecycle:

users·TypeScript
1
2function Users() {
3  const [users, setUsers] = useState<User[]>([]);
4  const [loading, setLoading] = useState(true);
5  const [error, setError] = useState<Error | null>(null);
6  
7  useEffect(() => {
8    let ignore = false;
9    
10    async function loadUsers() {
11      try {
12          setLoading(true);
13          const response = await fetch("/api/users");
14          if (!response.ok) throw new Error("Failed to fetch");
15          
16          const data = await response.json();
17          if (!ignore) setUsers(data);
18      } catch (err) {
19            if (!ignore) setError(err as Error);
20      } finally {
21          if (!ignore) setLoading(false);
22          }
23      }
24    
25    loadUsers();
26    return () => { ignore = true; };
27    }, []);
28  
29  if (loading) return <Spinner />;
30  if (error) return <ErrorMessage error={error} />;
31  
32  return <UserList users={users} />;
33}


The component is making the request, managing loading/error states, handling cancellation, and preventing post-unmount updates. It's noisy and repetitive.

The TanStack Query Approach


Now, let's refactor this using the mental model we just discussed:

users·TypeScript
1
2function Users() {
3// Reading data -> useQuery
4const { data: users, isLoading, error } = useQuery({
5    queryKey: ["users"],
6    queryFn: getUsers,
7    });
8
9if (isLoading) return <Spinner />;
10if (error) return <ErrorMessage error={error} />;
11
12return <UserList users={users} />;
13}

That’s it. Your component now only describes what data it needs. TanStack Query handles how that data stays synchronized.

The Philosophy of Complexity

Don't add TanStack Query just because everyone else does. Every library adds complexity. If your app only makes one or two API calls, useEffect is often enough.

Use TanStack Query when it removes more code than it adds. Every dependency you introduce carries a hidden cost: future version upgrades, additional documentation to read, potential bugs, and onboarding time for new team members. Tools should remove code, not create ceremonies.

Key Takeaway

A great engineer doesn't choose the newest tool just because it's trending. They choose the simplest tool that solves today's problem while leaving room for tomorrow's scale.

useEffect isn't obsolete. TanStack Query isn't mandatory. They are simply different tools designed to solve different problems.

Understanding the distinction between client-side effects and server state—and knowing exactly when to apply each—is far more valuable than memorizing the API of the latest library.

Related Articles

How to write better UGC video hooks

How to write better UGC video hooks

Stop losing viewers in the first three seconds. This guide breaks down the psychology of "Expectation vs. Reality" and the "Speed to Value" framework to help you engineer high-retention UGC hooks. Learn how to build curiosity loops and use contrast to turn passive scrollers into engaged viewers.

Read More
Core Web Vitals (2025): Complete Guide to Improve LCP, INP & CLS — Checklist + Fixes

Core Web Vitals (2025): Complete Guide to Improve LCP, INP & CLS — Checklist + Fixes

Practical 2025 guide to improve Core Web Vitals (LCP, INP, CLS). Step-by-step fixes, checklist, tools and code snippets to boost UX, SEO and conversions.

Read More
Ai for software development

Ai for software development

AI is revolutionizing software development, automating tasks, improving code quality, and enhancing user experiences. As demand for AI tools surges, developers are seeking smarter, faster, and more efficient ways to build robust applications

Read More