redux-query vs axios vs react-query vs swr
Data Fetching and Caching Strategies for React Applications
redux-queryaxiosreact-queryswrSimilar Packages:

Data Fetching and Caching Strategies for React Applications

axios is a promise-based HTTP client used for making requests from both Node.js and browsers. react-query (now superseded by @tanstack/react-query) and swr are React hooks libraries that handle data fetching, caching, synchronization, and revalidation automatically. redux-query is a middleware that integrates data fetching logic directly into a Redux store, managing request state alongside application state. While axios handles the transport layer, the other three manage how that data is stored, updated, and reflected in the UI.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
redux-query70,7601,098197 kB132 years agoMIT
axios0108,6542.42 MB34819 days agoMIT
react-query048,8572.26 MB1493 years agoMIT
swr032,340310 kB18819 days agoMIT

Axios vs React Query vs Redux Query vs SWR: Data Fetching Architectures

When building React applications, deciding how to fetch and manage server data is a critical architectural choice. axios provides the raw network capability, while react-query, redux-query, and swr offer higher-level abstractions to handle caching, loading states, and synchronization. Let's compare how they tackle common engineering problems.

πŸ“‘ Fetching Data: Manual Effects vs Dedicated Hooks

axios requires you to manage the request lifecycle manually using React effects.

  • You must track loading and error states yourself.
  • No built-in caching means repeated renders trigger repeated requests.
// axios: Manual fetch in useEffect
import axios from 'axios';
import { useEffect, useState } from 'react';

function UserProfile({ id }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    axios.get(`/api/users/${id}`)
      .then(res => setData(res.data))
      .finally(() => setLoading(false));
  }, [id]);

  if (loading) return <div>Loading...</div>;
  return <div>{data?.name}</div>;
}

react-query uses a dedicated hook to handle fetching and caching automatically.

  • Loading and error states are returned directly from the hook.
  • Requests are deduplicated and cached by default.
// react-query: useQuery hook
import { useQuery } from 'react-query';
import axios from 'axios';

function UserProfile({ id }) {
  const { data, isLoading, error } = useQuery(
    ['user', id],
    () => axios.get(`/api/users/${id}`).then(res => res.data)
  );

  if (isLoading) return <div>Loading...</div>;
  return <div>{data?.name}</div>;
}

redux-query maps query state to props or hooks via the Redux store.

  • You define queries in a configuration object.
  • State is accessed through the global store connection.
// redux-query: useReduxQuery hook
import { useReduxQuery } from 'react-redux-query';

const getUserQuery = (id) => ({
  url: `/api/users/${id}`,
  transform: (res) => res.data,
  queryKey: ['user', id],
});

function UserProfile({ id }) {
  const [{ data }, query] = useReduxQuery(getUserQuery(id));
  // query.requestStatus indicates loading state
  
  if (query.requestStatus === 'pending') return <div>Loading...</div>;
  return <div>{data?.name}</div>;
}

swr provides a lightweight hook similar to React Query but with a simpler API.

  • The first argument is the key, the second is the fetcher.
  • Automatically revalidates on focus or reconnect.
// swr: useSWR hook
import useSWR from 'swr';
import axios from 'axios';

const fetcher = (url) => axios.get(url).then(res => res.data);

function UserProfile({ id }) {
  const { data, error, isLoading } = useSWR(`/api/users/${id}`, fetcher);

  if (isLoading) return <div>Loading...</div>;
  return <div>{data?.name}</div>;
}

πŸ—„οΈ Caching: None vs Automatic vs Store-Based

axios has no built-in caching mechanism.

  • Every component mount triggers a new network request.
  • You must implement local storage or context wrappers to cache results.
// axios: No caching (Manual implementation required)
// Every render cycle with useEffect triggers a new GET request
useEffect(() => {
  axios.get('/api/data'); // Always hits network
}, []);

react-query caches responses in memory by query key.

  • Data persists between component unmounts and remounts.
  • Stale data is shown while background updates fetch fresh content.
// react-query: Automatic caching
// Data is cached under ['todos'] key
const { data } = useQuery(['todos'], fetchTodos);
// Unmounting does not clear cache immediately

redux-query stores fetched data in the Redux state tree.

  • Caching depends on how you structure your Redux reducers.
  • Data persists as long as the Redux store persists.
// redux-query: Redux store caching
// Data lives in redux state under entities.users
// Managed by redux-query middleware transformations
const entities = useSelector(state => state.entities);

swr caches data in memory with a built-in provider.

  • Shares cache across components using the same key.
  • Supports time-based revalidation configurations.
// swr: Built-in cache
// Cache is shared globally for the key
const { data } = useSWR('/api/data', fetcher);
// Configure revalidation time
const { data } = useSWR('/api/data', fetcher, { revalidateOnFocus: false });

✍️ Updating Data: Promises vs Mutations vs Dispatch

axios relies on standard promise chains for mutations.

  • You manually update local state after a successful request.
  • No automatic invalidation of related queries.
// axios: Manual mutation
const updateUser = async (id, name) => {
  await axios.put(`/api/users/${id}`, { name });
  // Manually update local state or reload page
  setUser({ ...user, name });
};

react-query provides useMutation for side effects.

  • Can automatically invalidate queries to trigger refetches.
  • Handles loading and error states for the mutation separately.
// react-query: useMutation
const mutation = useMutation(
  (newData) => axios.put('/api/user', newData),
  {
    onSuccess: () => {
      queryClient.invalidateQueries(['user']);
    }
  }
);

redux-query handles mutations via Redux actions.

  • You dispatch an action that triggers the middleware request.
  • Optimistic updates require manual reducer logic.
// redux-query: Dispatch mutation
const updateUser = (id, name) => {
  dispatch({
    type: 'UPDATE_USER',
    query: { url: `/api/users/${id}`, method: 'PUT', body: { name } }
  });
};

swr uses the mutate function to update cache and server.

  • Can optimistically update the UI before the request finishes.
  • Requires manual revalidation or cache manipulation.
// swr: mutate function
import useSWR, { mutate } from 'swr';

const updateUser = async (id, name) => {
  await axios.put(`/api/users/${id}`, { name });
  mutate('/api/users/' + id, { ...data, name }, false);
};

⚠️ Error Handling: Try/Catch vs Hook States

axios throws errors that must be caught in try/catch blocks.

  • You decide how to display errors in the UI.
  • No standardized error object structure across the app.
// axios: Try/Catch block
try {
  await axios.get('/api/data');
} catch (err) {
  console.error(err.response?.data);
  // Manual UI error state update
}

react-query returns error objects directly from the hook.

  • Errors are serialized and safe to render.
  • Retry logic is built-in and configurable.
// react-query: Error from hook
const { error } = useQuery(['data'], fetchData);
if (error) return <div>Error: {error.message}</div>;

redux-query stores error state in the Redux store.

  • You select error state from the global store.
  • Requires boilerplate to map errors to UI components.
// redux-query: Error from state
const [{ isErrored }] = useReduxQuery(myQuery);
if (isErrored) return <div>Request Failed</div>;

swr returns error objects similar to React Query.

  • Errors trigger revalidation retries by default.
  • Simple boolean flags for easy conditional rendering.
// swr: Error from hook
const { error } = useSWR('/api/data', fetcher);
if (error) return <div>Failed to load</div>;

🀝 Similarities: Shared Ground Between Libraries

While the approaches differ, these libraries share common goals and underlying technologies.

1. βš›οΈ All Work Within the React Ecosystem

  • Except axios (which is framework-agnostic), all provide React-specific hooks.
  • Support functional components and hooks as the primary interface.
// Shared: Functional Component Pattern
function MyComponent() {
  // All libraries (except raw axios) provide hooks here
  return <div>Data Display</div>;
}

2. 🌐 Promise-Based Under the Hood

  • All rely on JavaScript Promises for async operations.
  • axios returns promises; others wrap them for state management.
// Shared: Promise Resolution
// axios returns promise
axios.get('/url').then(res => ...);

// Others accept promise-returning functions
useQuery('key', () => axios.get('/url').then(res => res.data));

3. πŸ›‘οΈ Error Propagation

  • All allow catching network errors (4xx, 5xx).
  • Provide mechanisms to handle failure states in the UI.
// Shared: Error Handling
// axios
.catch(err => handleError(err));

// react-query/swr
if (error) showErrorMessage();

4. πŸ”Œ Extensibility

  • All support interceptors or custom fetchers.
  • Allow adding auth tokens or logging globally.
// axios: Interceptors
axios.interceptors.request.use(config => {
  config.headers.Auth = getToken();
  return config;
});

// swr: Custom Fetcher
const fetcher = (url) => axios.get(url, { headers: { Auth: getToken() } });

πŸ“Š Summary: Key Differences

Featureaxiosreact-queryredux-queryswr
TypeHTTP ClientData HookRedux MiddlewareData Hook
Caching❌ Noneβœ… Automaticβœ… Redux Storeβœ… Automatic
BoilerplateHigh (Manual)LowHigh (Redux)Very Low
React IntegrationNone (Manual)Deep (Hooks)Deep (Connect/Hooks)Deep (Hooks)
Mutation ToolsManual PromisesuseMutationDispatch Actionsmutate Function
Bundle WeightLightweightHeavierHeavy (Redux + Lib)Lightweight

πŸ’‘ The Big Picture

axios is the foundation 🧱 β€” it handles the network traffic but leaves state management to you. Use it when you need raw control or are not using React.

react-query is the powerhouse πŸ‹οΈ β€” it manages complex server state with robust tools for caching, mutations, and background sync. Best for large-scale apps.

redux-query is the integrator πŸ”— β€” it fits data fetching into an existing Redux flow. Best for legacy apps already committed to Redux.

swr is the sprinter πŸƒ β€” it offers fast setup and lightweight caching. Best for Next.js projects or simpler data needs.

Final Thought: If you are starting a new React project today, react-query (TanStack Query) or swr are the modern standards. axios remains essential as the underlying transport for both. Reserve redux-query for maintaining existing Redux-heavy codebases.

How to Choose: redux-query vs axios vs react-query vs swr

  • redux-query:

    Choose redux-query if your application already relies heavily on Redux for all state management and you want data fetching to follow the same unidirectional data flow. It suits legacy enterprise apps where introducing a new data caching library would create too much architectural fragmentation.

  • axios:

    Choose axios if you need a low-level HTTP client for non-React environments or want full manual control over request lifecycle without built-in caching. It is ideal for simple scripts, backend Node.js services, or when you plan to build your own caching layer on top of raw promises.

  • react-query:

    Choose react-query (or its successor @tanstack/react-query) if you need powerful caching, background updates, and robust mutation handling for complex applications. It is the best fit for dashboards or data-heavy apps where server state management needs to be separated from client UI state.

  • swr:

    Choose swr if you want a lightweight, fast-to-setup data fetching library with automatic revalidation and focus on simplicity. It is perfect for Next.js projects or smaller applications where you need caching benefits without the heavier configuration of React Query.

README for redux-query

redux-query

npm

A library for managing network state in Redux.

Why use redux-query?

  • It's simply Redux: Follow best practices for storing and handling network state in Redux, with support for features like optimistic updates and cancellation. There's no magic here, just middleware, actions, selectors, and reducers.
  • It's extensible: Built to fit most use cases out-of-the-box, but can easily be extended with custom Redux middleware, UI integrations, and network interfaces.
  • It works great with React: With the provided React hooks and higher-order component in redux-query-react (optional), colocate data dependencies with your components and run requests when components mount or update.

Docs

Redux API

React API

Examples

  • Simple example: This example is a very simple web app that has only one feature – you can view and update your username. The purpose of this example is to demonstrate how requests and mutations (including optimistic updates) work with redux-query.
  • Hacker News: This example shows how to use redux-query, redux-query-react, and redux-query-interface-superagent to build a basic Hacker News client.

License

MIT

About

Brought to you by Amplitude Engineering. We're hiring!