DEV Community

Cover image for Stop Using useEffect for Data Fetching
Devesh Sangwan
Devesh Sangwan

Posted on

Stop Using useEffect for Data Fetching

A few weeks ago, the React community was buzzing after the useEffect debacle at Cloudflare.

If you’re not aware of what happened, don’t worry — I’ll briefly explain it here.

But the key takeaway for me was this:

We’ve been misusing useEffect for data fetching all along.

Sure, useEffect can fetch data, but it quickly becomes messy — you end up juggling loading states, error handling, cleanup, race conditions, and caching manually. It’s boilerplate-heavy, error-prone, and far from ideal.

That’s where TanStack Query (formerly React Query) comes in.
Instead of reinventing the wheel, it gives us powerful, battle-tested hooks that handle loading, errors, caching, retries, and background refetching — all out of the box.

In this post, I’ll walk through:

  1. 🧩 What happened at Cloudflare
  2. ⚠️ Why useEffect isn’t the right tool for data fetching
  3. 💪 How TanStack Query makes your life as a React developer much easier

🧩 What Happened at Cloudflare?

So, what exactly went wrong?

In their own post-mortem, the Cloudflare team explained that a bug in their React dashboard caused massive unintended API traffic.

“…we mistakenly included a problematic object in its dependency array. Because this object was recreated on every state or prop change, React treated it as ‘always new,’ causing the useEffect to re-run each time. As a result, the API call executed many times during a single dashboard render instead of just once.”

Think about that — a tiny mistake in a dependency array triggered a flood of API calls.

It didn’t just make the UI laggy; it completely overwhelmed their backend authorization service, leading to a major outage across multiple APIs.

This is the perfect example of why managing data fetching with useEffect is risky.

It puts the burden of dependency tracking, cleanup, and request control entirely on you — the developer.

TanStack Query eliminates this entire class of problems by design. It ensures that a simple re-render never floods your backend or breaks your app.


⚙️ The Traditional Way (Using useEffect)

Here’s a typical data-fetching example using useEffect.

Notice how much manual state management is involved.

import { useState, useEffect } from 'react';

// Simulated fetch function
const fetchUser = async () => {
  const response = await fetch('https://api.github.com/users/deveshsangwan');
  if (!response.ok) throw new Error('Network response was not ok');
  return response.json();
};

function UserProfile() {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;

    const getUser = async () => {
      try {
        const userData = await fetchUser();
        if (isMounted) setUser(userData);
      } catch (err) {
        if (isMounted) setError(err.message);
      } finally {
        if (isMounted) setIsLoading(false);
      }
    };

    getUser();
    return () => { isMounted = false; };
  }, []);

  if (isLoading) return <div>Loading profile...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

🔍 Key Problems

  • Manual state management (isLoading, error, user)
  • Cleanup logic to prevent race conditions
  • Repeated boilerplate across components

It works — but at scale, it’s fragile and repetitive.


✅ The TanStack Query Way (The “Right” Way)

Here’s the same functionality using TanStack Query.

Notice how much cleaner and more declarative it is.

First, set up the QueryClientProvider at the root of your app:

// App.jsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import UserProfile from './UserProfile';

const queryClient = new QueryClient();

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserProfile />
    </QueryClientProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now the component itself becomes drastically simpler:

import { useQuery } from '@tanstack/react-query';

const fetchUser = async () => {
  const response = await fetch('https://api.github.com/users/deveshsangwan');
  if (!response.ok) throw new Error('Network response was not ok');
  return response.json();
};

function UserProfile() {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', 'deveshsangwan'],
    queryFn: fetchUser,
  });

  if (isLoading) return <div>Loading profile...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Let's quickly break down the two core options we used in that useQuery hook:

queryKey: ['user', 'deveshsangwan']
Enter fullscreen mode Exit fullscreen mode

This is the unique identifier for this query. TanStack Query uses it internally for caching and refetching. The key must be a serializable array, which is the direct solution to the Cloudflare issue. Instead of an unstable object reference in a dependency array, we use a stable, predictable key.

queryFn: fetchUser
Enter fullscreen mode Exit fullscreen mode

This is simply the asynchronous function that fetches the data. It must return a promise that either resolves with the data or throws an error.

🌟 Benefits

  • No useEffect or manual state needed
  • Built-in caching and background refetching
  • Automatic handling of loading and error states
  • Cleaner, more maintainable code

💭 Final Thoughts

The Cloudflare incident reminded the React community that just because something works doesn’t mean it’s right.

useEffect wasn’t designed for data fetching — and trying to force it into that role introduces subtle, hard-to-debug problems.

TanStack Query lets you focus on what matters — the UI — while handling the rest for you.

Once you switch, you’ll never want to go back.

Learn more on the official TanStack Query documentation.


Top comments (0)