xyruscode

Mastering Axios + React Query Invalidation for Better Data Fetching

·3 min read·--- views
reactaxiosreact-query

When building React apps, data fetching is one of the trickiest parts to get right. You need to fetch, cache, revalidate, and sometimes manually update your data when the server changes. That’s where React Query comes in—it handles caching and synchronization like a champ. And when you pair it with Axios, you get a flexible and powerful setup for API requests.

In this post, I’ll walk you through using Axios with React Query and how to invalidate queries effectively to keep your UI fresh and consistent.


Why React Query + Axios?

React Query isn’t tied to any HTTP client. It works perfectly with fetch, Axios, or anything else. But Axios has benefits like:

  • Request/response interceptors (great for auth tokens, error logging).
  • Automatic JSON parsing.
  • A clean API for handling headers, params, and cancellation.

So, using Axios as your transport layer and React Query for state management is a winning combo.


Setting Up Axios with React Query

First, create a reusable Axios instance so you don’t repeat configurations:

// apiClient.ts
import axios from "axios";

const apiClient = axios.create({
  baseURL: "https://api.example.com",
  headers: {
    "Content-Type": "application/json",
  },
});

// Example interceptor
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    console.error("API Error:", error);
    return Promise.reject(error);
  }
);

export default apiClient;

Then wrap your app in a QueryClientProvider:

// main.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ReactDOM from "react-dom/client";
import App from "./App";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")!).render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
);

Fetching Data with React Query

Fetching data becomes simple:

import { useQuery } from "@tanstack/react-query";
import apiClient from "./apiClient";

function useUsers() {
  return useQuery({
    queryKey: ["users"],
    queryFn: async () => {
      const { data } = await apiClient.get("/users");
      return data;
    },
  });
}

And in your component:

function UsersList() {
  const { data, isLoading } = useUsers();

  if (isLoading) return <p>Loading...</p>;
  return (
    <ul>
      {data.map((user: any) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Mutations and Invalidation

The magic happens when you mutate data (e.g., creating a new user). After a successful mutation, you don’t want stale data lingering in your UI—you want to invalidate the old cache and re-fetch the latest data.

Here’s how:

import { useMutation, useQueryClient } from "@tanstack/react-query";
import apiClient from "./apiClient";

function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (newUser: { name: string }) => {
      const { data } = await apiClient.post("/users", newUser);
      return data;
    },
    onSuccess: () => {
      // Invalidate the "users" query to refetch fresh data
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
  });
}

And in a component:

function AddUserForm() {
  const createUser = useCreateUser();

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    const form = e.target as HTMLFormElement;
    const name = (form.elements.namedItem("name") as HTMLInputElement).value;

    createUser.mutate({ name });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" placeholder="Enter name" />
      <button type="submit">Add User</button>
    </form>
  );
}

Why Invalidation Matters

Without invalidation, you’d need to manually refetch or manage state updates. That’s error-prone and messy. With React Query:

  • Consistency: The UI always reflects the latest server state.
  • Performance: React Query batches requests and prevents unnecessary fetching.
  • Simplicity: Just tell it which query is now stale—React Query handles the rest.

Wrapping Up

By combining Axios for requests and React Query for caching and invalidation, you get a reliable, maintainable, and scalable data fetching setup.

  • Use Axios to centralize API logic.
  • Use React Query hooks for fetching.
  • Invalidate queries after mutations to keep your data fresh.

If your React app is dealing with any kind of server state, this setup will save you a lot of headaches.

Comments