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.
React Query isn’t tied to any HTTP client. It works perfectly with fetch, Axios, or anything else. But Axios has benefits like:
So, using Axios as your transport layer and React Query for state management is a winning combo.
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 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>
);
}
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>
);
}
Without invalidation, you’d need to manually refetch or manage state updates. That’s error-prone and messy. With React Query:
By combining Axios for requests and React Query for caching and invalidation, you get a reliable, maintainable, and scalable data fetching setup.
If your React app is dealing with any kind of server state, this setup will save you a lot of headaches.
A passionate developer with 5+ years of experience in web development. Specializing in React, TypeScript, and modern JavaScript frameworks.
View all posts by Prince ShammahNo related articles found.
Get notified when new articles are published.