$ xyruscodev7

Blog

Mastering Axios + React Query Invalidation for Better Data Fetching

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.

·10 min read
ReactTypeScriptData Fetching

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.response?.data);
    return Promise.reject(error);
  }
);

export default apiClient;

Creating a Query Hook

Let's say you have a /users endpoint. Here's how you'd fetch and cache the data:

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

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

React Query caches the result automatically. If you navigate away and come back, it serves from cache while refetching in the background.


When to Invalidate

Invalidation tells React Query: "This cache is stale — refetch it next time someone asks." You'll want to invalidate after:

  • Creating, updating, or deleting a resource
  • Login/logout
  • Any mutation that changes server state

Invalidating After a Mutation

Here's a practical example — creating a user and invalidating the users list:

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

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

  return useMutation({
    mutationFn: (newUser: { name: string; email: string }) =>
      apiClient.post("/users", newUser).then((res) => res.data),
    onSuccess: () => {
      // Invalidate the users list so it refetches
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
  });
}

When onSuccess fires, any component using useUsers() will automatically refetch the latest data.


Selective Invalidation

You don't always want to refetch everything. React Query supports granular invalidation:

// Invalidate only a specific user
queryClient.invalidateQueries({
  queryKey: ["users", userId],
});

// Invalidate all queries starting with "users"
queryClient.invalidateQueries({
  queryKey: ["users"],
  exact: false,
});

Optimistic Updates

For a snappy UX, you can update the cache before the server responds:

useMutation({
  mutationFn: updateUser,
  onMutate: async (updatedUser) => {
    await queryClient.cancelQueries({ queryKey: ["users"] });

    const previousUsers = queryClient.getQueryData(["users"]);

    queryClient.setQueryData(["users"], (old) =>
      old.map((u) => (u.id === updatedUser.id ? updatedUser : u))
    );

    return { previousUsers };
  },
  onError: (err, updatedUser, context) => {
    queryClient.setQueryData(["users"], context.previousUsers);
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ["users"] });
  },
});

Conclusion

Pairing Axios with React Query gives you a robust, cache-aware data fetching layer. The key insight is that invalidation is how you tell React Query when to refetch — not polling, not manual refetch calls, but targeted invalidation after mutations.

Get this right, and your UI will always feel fresh without unnecessary network requests.

Comments