# React Query vs Redux: Server-State and Client-State Management in React

> Compare React Query vs Redux for server-state and client-state management. Learn how React Query handles caching while Redux manages UI logic to optimize your React app.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: comparison
- Published: 2026-02-16

---

**React Query (TanStack Query) specializes in declarative server-state management with automatic caching and synchronization, while Redux provides a predictable client-state container for complex UI logic, and many production applications use both libraries together.**

When architecting state management for React applications, understanding the distinction between server-derived state and client-derived state is essential. While the `facebook/react` repository includes Redux integration examples in its fixtures for testing purposes, React Query operates as an external library that focuses exclusively on asynchronous server-state synchronization.

## Core Architectural Differences

### Server State vs Client State

**React Query** treats server state as the primary concern. It manages data that originates from an external API, handling the entire lifecycle of asynchronous operations including loading states, error handling, caching, and background synchronization. The library assumes the server is the source of truth.

**Redux**, as implemented in examples like [`fixtures/nesting/src/store.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/store.js) within the React repository, manages client-side state. This includes UI state (theme preferences, modal visibility), business logic state, and any data that must persist across components without necessarily originating from a server request.

### Data Synchronization Strategies

React Query provides **automatic synchronization** features out-of-the-box. When configured with `refetchOnWindowFocus: true` or interval polling, the library handles refetching data when the application regains focus or based on time intervals. This eliminates manual synchronization logic.

Redux requires **manual dispatch patterns** to keep data synchronized. After a mutation, developers must explicitly dispatch actions to update the store, as shown in the test files like [`packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js`](https://github.com/facebook/react/blob/main/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js) where Redux integration requires specific hook inspection patterns.

## Boilerplate and Developer Experience

React Query minimizes boilerplate through hooks like `useQuery` and `useMutation`. Setup requires only wrapping the application in a `QueryClientProvider`:

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

```

Redux demands more initial configuration. Based on the pattern in [`fixtures/nesting/src/store.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/store.js), developers must create a store, define reducers, and potentially configure middleware:

```javascript
// fixtures/nesting/src/store.js pattern
import { createStore } from 'redux';

const initialState = { todos: [] };

function todoReducer(state = initialState, action) {
  switch (action.type) {
    case 'SET_TODOS':
      return { ...state, todos: action.payload };
    default:
      return state;
  }
}

export const store = createStore(todoReducer);

```

## Caching and Performance

### Automatic Caching in React Query

React Query implements **query key-based caching** with configurable `staleTime`. The library automatically deduplicates requests with identical query keys and serves cached data while fetching fresh data in the background:

```tsx
const { data, isLoading } = useQuery(
  ['todos'],           // query key for caching
  fetchTodos,
  {
    staleTime: 5 * 60 * 1000,  // 5 minutes
    cacheTime: 10 * 60 * 1000  // garbage collection time
  }
);

```

### Manual Implementation in Redux

Redux provides **no built-in caching mechanisms**. To implement similar behavior, developers must manually track loading states and memoize selectors using additional libraries like Reselect, or implement custom logic to prevent duplicate fetches.

## Code Examples: Fetching Data

### React Query Implementation

This example demonstrates declarative data fetching with automatic loading and error states:

```tsx
import { useQuery, useMutation, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import axios from 'axios';

const queryClient = new QueryClient();

function Todos() {
  const { data, error, isLoading } = useQuery(
    ['todos'],
    async () => {
      const res = await axios.get('/api/todos');
      return res.data;
    },
    {
      staleTime: 5 * 60 * 1000,
      refetchOnWindowFocus: true,
    }
  );

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Error loading todos.</p>;

  return (
    <ul>
      {data.map((todo: any) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  );
}

```

### Redux Implementation

This example shows the equivalent pattern using Redux, based on structures found in the React repository fixtures:

```tsx
// store.ts
import { createStore } from 'redux';

interface State {
  todos: any[];
  loading: boolean;
  error: string | null;
}

const initialState: State = {
  todos: [],
  loading: false,
  error: null
};

function todoReducer(state = initialState, action: any): State {
  switch (action.type) {
    case 'FETCH_TODOS_START':
      return { ...state, loading: true, error: null };
    case 'FETCH_TODOS_SUCCESS':
      return { ...state, loading: false, todos: action.payload };
    case 'FETCH_TODOS_ERROR':
      return { ...state, loading: false, error: action.error };
    default:
      return state;
  }
}

export const store = createStore(todoReducer);

```

```tsx
// Todos.tsx
import { useEffect } from 'react';
import { useSelector, useDispatch, Provider } from 'react-redux';
import { store } from './store';
import axios from 'axios';

function Todos() {
  const { todos, loading, error } = useSelector((state: any) => state);
  const dispatch = useDispatch();

  useEffect(() => {
    async function loadTodos() {
      dispatch({ type: 'FETCH_TODOS_START' });
      try {
        const res = await axios.get('/api/todos');
        dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: res.data });
      } catch (err) {
        dispatch({ type: 'FETCH_TODOS_ERROR', error: 'Failed to load' });
      }
    }
    loadTodos();
  }, [dispatch]);

  if (loading) return <p>Loading…</p>;
  if (error) return <p>{error}</p>;

  return (
    <ul>
      {todos.map((todo: any) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

export default function App() {
  return (
    <Provider store={store}>
      <Todos />
    </Provider>
  );
}

```

## When to Use React Query vs Redux

Choose **React Query** when:
- Your application primarily fetches, caches, and synchronizes server data
- You need automatic background refetching, pagination, or optimistic updates
- You want to eliminate loading state boilerplate
- The server is the source of truth for your data

Choose **Redux** when:
- You have complex client-side state unrelated to server data (themes, UI modes, complex forms)
- You need deterministic state transitions with time-travel debugging
- Your application requires complex middleware chains for side effects
- You are building a library where consumers need predictable state containers

Many production applications, including those tested in the React repository, combine both approaches: Redux manages global UI state while React Query handles all server communication.

## React Repository References

The `facebook/react` repository contains several references to Redux integration patterns:

- **[`fixtures/nesting/src/store.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/store.js)**: Demonstrates a basic Redux store configuration used in React's nesting fixtures for testing component hierarchies with external state management.

- **[`packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js`](https://github.com/facebook/react/blob/main/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js)**: Contains test cases that inspect Redux-related hook integrations, showing how React DevTools interact with external state libraries.

- **[`packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js`](https://github.com/facebook/react/blob/main/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js)**: References Redux issues and integration patterns for debugging hooks in applications using external state containers.

These files illustrate that while React Query operates outside the core React repository, Redux has established integration patterns tested within React's official fixture suite.

## Summary

- **React Query** (TanStack Query) is a server-state management library that provides automatic caching, background refetching, and declarative data fetching through hooks like `useQuery` and `useMutation`.

- **Redux** is a client-state container that requires manual action dispatching and reducer logic, offering deterministic state transitions and time-travel debugging for complex UI state.

- The `facebook/react` repository includes Redux integration examples in [`fixtures/nesting/src/store.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/store.js) and test files like [`ReactHooksInspectionIntegration-test.js`](https://github.com/facebook/react/blob/main/ReactHooksInspectionIntegration-test.js), while React Query exists as an external specialized library.

- Most modern React applications benefit from using **both** libraries together: React Query for server data synchronization and Redux (or Redux Toolkit) for global client-side state.

## Frequently Asked Questions

### Can I use React Query and Redux together in the same application?

Yes, many production applications use both libraries simultaneously. React Query handles server-state fetching, caching, and synchronization, while Redux manages client-only state like UI themes, modal visibility, or complex form state. This separation of concerns allows each tool to excel at its specific domain without conflict.

### Does React Query replace Redux entirely?

No, React Query does not replace Redux for client-state management. While React Query can eliminate the need for Redux when handling server data, Redux remains essential for complex client-side business logic, state that must persist across sessions, or applications requiring deterministic state transitions with middleware chains.

### Which library has better TypeScript support?

Both libraries offer excellent TypeScript support, but with different ergonomic approaches. React Query provides type inference through its hook APIs, automatically inferring types from fetch functions. Redux Toolkit, the modern standard for Redux development, includes advanced type utilities for actions and reducers, though it requires more explicit type definitions for store configuration.

### How do the testing approaches differ between React Query and Redux?

Testing Redux involves mocking the store, dispatching actions, and asserting state changes in reducers, as demonstrated in [`packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js`](https://github.com/facebook/react/blob/main/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js) within the React repository. React Query testing focuses on mocking API responses and verifying hook behaviors using utilities like `renderHook` from React Testing Library, with the library handling caching and background updates automatically.