Managing Backend State with React Query: Why Redux Isn’t Always the Answer

¡ 9 min

For years, Redux has been the default choice for managing application state in React apps. It’s powerful and flexible, but let’s be honest—it can be overkill, especially when you’re dealing primarily with backend (server) state like fetching and caching API data.

If you’ve found yourself drowning in Redux boilerplate to fetch and store backend data, you’re not alone. Enter React Query—a simpler, cleaner, and more maintainable solution for backend state.

Let’s dive deep into how React Query can streamline your workflow, reduce boilerplate, and why it might just be the alternative you’ve been waiting for.

Why Redux Isn’t Ideal for Backend State

Redux is fantastic for managing complex UI state (like themes, modals, or forms). However, when it comes to backend data—fetched from APIs and databases—Redux can quickly become cumbersome:

In short:

“Redux is a Swiss Army knife—but sometimes you just need a butter knife.”

Introducing React Query: Simplifying Backend State

React Query isn’t just another state management library. It’s specifically designed for fetching, caching, synchronizing, and updating your backend data seamlessly and efficiently.

Key React Query Features:

Let’s see it in action.

🛠️ Getting Started: Fetching Data with React Query

Step 1: Installation

Install React Query into your project:

npm i @tanstack/react-query

Step 2: Setting Up the Query Client

Add the React Query provider at your app root (usually in index.js or App.js):

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

const queryClient = new QueryClient();

ReactDOM.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  document.getElementById('root')
);

Step 3: Fetch Data with useQuery Hook

Fetching data is incredibly straightforward with React Query:

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

const fetchUsers = async () => {
  const res = await fetch('https://api.example.com/users');
  return res.json();
};

const UsersList = () => {
  const { data, error, isLoading } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error loading users.</div>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

Notice how concise this code is—no reducers, no actions, no thunks. React Query handles caching and state internally, significantly simplifying your codebase.

Mutating Data (CRUD Operations) with React Query

React Query handles CRUD operations seamlessly using its useMutation hook.

Example: Creating a new user

import { useMutation, useQueryClient } from '@tanstack/react-query';

const addUser = async (newUser) => {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(newUser),
  });
  return response.json();
};

const AddUser = () => {
  const queryClient = useQueryClient();
  const mutation = useMutation({ mutationFn: addUser, onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] });
  }});

  const handleAdd = () => {
    mutation.mutate({ name: 'New User' });
  };

  return <button onClick={handleAdd}>Add User</button>;
};

After adding a user, React Query automatically refreshes your user list without you manually handling cache invalidation or refetch logic.

React Query vs. Redux: A Quick Comparison

FeatureReact QueryRedux
Backend state handlingAutomatic cachingManual implementation
Data fetching logicMinimal boilerplateHeavy boilerplate
Async state handlingBuilt-inManual handling required
Optimistic updatesEasy setupRequires custom logic
Best forAPI data & CRUDComplex client-side logic

When should you pick Redux?

When should you pick React Query?

⚠️ Things to Keep in Mind

While React Query is powerful, a few considerations apply:

Real-World Applications

React Query is ideal for:

📚 Further Resources

🏁 Final Thoughts

If you’re struggling to manage backend state with Redux, it might be time to reconsider your tools. React Query provides a simpler, more effective solution specifically designed for handling server-state. With built-in caching, streamlined async handling, and minimal boilerplate, React Query can significantly simplify your frontend architecture.

Is React Query always better than Redux? No. But for backend state management, it’s a clear winner—making your code cleaner, maintainable, and significantly easier to reason about.

If you already have legacy Redux code managing backend state, consider migrating to RTK Query—it provides similar benefits to React Query but integrates seamlessly within your existing Redux setup.

On the other hand, if you’re starting a fresh React project, pairing React Query with a simpler alternative to Redux for global UI state might be your best bet. We’ll dive into these modern alternatives tomorrow, exploring options that complement React Query beautifully and simplify your state management even further.

Give React Query or RTK Query a try in your next project—you might just find yourself never looking back.