DEV Community

Cover image for I Used Redux for API Data… Until React Query Saved Me
D. Dhanushka
D. Dhanushka

Posted on

I Used Redux for API Data… Until React Query Saved Me

It’s 2026.
And please… don’t be a stupid developer like me.

Yes, I said it 😄

For a long time, I used Redux (and sometimes Context) for everything.
University projects? Redux.
Side projects? Redux.
Fetching data from APIs? Yup… Redux again.

Then one day, a senior dev casually asked me:

“Bro… are you still using Redux to manage server state?”

I was like:
“Server… state? Isn’t state just… state?” 🤯

Turns out — nope.
And that question sent me down a rabbit hole that introduced me to React Query (now TanStack Query).

At that time, I was proudly pouring API responses into Redux like Dumbledore saying
“Of course I know what I’m doing.”
(First-year engineer confidence is undefeated.)

This article is my attempt to end the confusion between client state and server state, without turning this into a boring 40-minute read.


🧠 What the heck is Server State?

Server state is data that comes from somewhere outside your app — usually an API.

It’s data that:

  • Comes from an API
  • Can be cached
  • Can become stale
  • Can be refetched
  • Can change without you doing anything (thanks other users 👋)

Examples:

  • Product list
  • User profile from backend
  • Orders
  • Search results
  • Paginated data

👉 React Query is built specifically to manage this kind of state


🎨 What is Client State then?

Client state lives purely inside your UI.

It’s data that:

  • Exists only in the browser
  • Is usually synchronous
  • Is not shared with other users

Examples:

  • Dark / light theme
  • Modal open / close
  • Selected filters (before calling an API)
  • UI auth state (not the API call itself)

👉 Redux still shines here


⚔️ Why this matters (and why Redux alone struggles)

Most web frameworks don’t tell you how to fetch and manage async data properly.
So we end up:

  • Writing useEffect + useState everywhere
  • Storing API responses in Redux
  • Manually handling loading, error, caching, refetching… again and again

Traditional state libraries are great for client state,
but server state is a different beast:

  • It lives remotely
  • It’s async
  • It can change without you knowing
  • It gets stale
  • It needs caching
  • It needs background updates
  • It needs pagination, deduping, memory management…

If you’re not overwhelmed by that list, congrats — you deserve an award 🏆
For the rest of us… this is where TanStack Query comes in.


🚀 Why React Query?

TanStack Query is hands-down one of the best tools for managing server state.

  • Works out of the box
  • Almost zero config
  • Handles the hard stuff for you
  • Scales as your app grows

Alright, enough theory.
Let’s see why React Query is actually better than the “classic React way”.


✅ Example 1: Fetching Data (useEffect vs useQuery)

❌ Classic React Way

const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

useEffect(() => {
  setLoading(true);
  fetchProducts()
    .then(res => setData(res))
    .catch(err => setError(err))
    .finally(() => setLoading(false));
}, []);
Enter fullscreen mode Exit fullscreen mode

Problems:

  • Boilerplate everywhere
  • Manual loading & error handling
  • No caching
  • No refetching

✅ React Query Way

const { data, isLoading, isError } = useQuery({
  queryKey: ['products'],
  queryFn: fetchProducts,
});
Enter fullscreen mode Exit fullscreen mode

🎉 Done.

  • Loading handled
  • Error handled
  • Cached
  • Refetched automatically

✅ Example 2: Caching (the silent killer)

❌ Classic Way

  • Navigate away → fetch again
  • Come back → fetch again
  • Same API call 10 times 🤦‍♂️

You either:

  • Accept it
  • Or write your own cache logic (good luck)

✅ React Query Way

useQuery({
  queryKey: ['products'],
  queryFn: fetchProducts,
  staleTime: 5 * 60 * 1000,
});
Enter fullscreen mode Exit fullscreen mode

React Query:

  • Caches data
  • Knows when data is stale
  • Refetches only when needed

You didn’t write any caching code.


✅ Example 3: Mutations & Refetching

❌ Classic Way (Redux / manual)

  • Call API
  • Update Redux
  • Refetch list
  • Sync UI manually

Lots of moving parts.


✅ React Query Way

const mutation = useMutation(addProduct, {
  onSuccess: () => {
    queryClient.invalidateQueries(['products']);
  },
});
Enter fullscreen mode Exit fullscreen mode

What happens:

  • API call succeeds
  • Cached data invalidated
  • List refetches automatically
  • UI updates itself

✨ Less code. Fewer bugs. Less stress.


🧩 Final Takeaway

If there’s one thing I wish someone had told me earlier, it’s this:

Not all state is the same.

Server state is messy, asynchronous, shared, and constantly changing.
Client state is local, predictable, and UI-focused.

Trying to manage both with the same tool (hello Redux-for-everything 👋) works…
but it also means writing a lot of code that React Query already solved.

So no, React Query doesn’t replace Redux.
It replaces painful server-state logic you shouldn’t be writing in 2026.

If this article made you question where your API data lives —
good. That’s exactly the point.

Now go try React Query.
Your future self (and your codebase) will thank you.


Top comments (0)