A big part of most modern frontend projects is fetching data from APIs and sending data back to a server. While you can do everything with the browser’s built-in fetch() API, many teams choose Axios to avoid common headaches and keep the workflow smoother.
Axios is:
- Not part of React (works with any framework or vanilla JS)
- A very popular HTTP client library
- Built around promises, with a clean API and great defaults
Docs: Axios Docs
Install Axios
NPM
npm install axios
CDN (quick demos)
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
Your First Request
Axios provides methods for the common HTTP verbs:
axios.get(url)axios.post(url, data)-
axios.put(url, data)/axios.patch(url, data) axios.delete(url)
Also, calling axios(url) defaults to a GET request.
Key notes:
- Axios returns a promise
- The response body is in
response.data - Errors often include server details in
error.response
import axios from "axios";
const url = "https://api.example.com/users";
const fetchData = async () => {
try {
// axios(url) defaults to GET
const response = await axios(url);
console.log("Full response:", response);
console.log("Data only:", response.data);
} catch (error) {
console.log("Error response:", error.response);
}
};
fetchData();
GET with Headers
Axios accepts a config object as the second argument in get():
const fetchDadJoke = async () => {
const url = "https://icanhazdadjoke.com/";
try {
const { data } = await axios.get(url, {
headers: {
Accept: "application/json",
},
});
console.log("Joke:", data.joke);
} catch (error) {
console.log(error.response);
}
};
fetchDadJoke();
Why this matters: some APIs return HTML by default unless you explicitly ask for JSON.
POST Request (Sending Data)
To send data to the server, use axios.post(url, data).
- Second argument is your payload
- Third argument is config (headers, auth, etc.)
import axios from "axios";
const url = "https://api.example.com/posts";
const createPost = async () => {
try {
const payload = { title: "Hello Axios", body: "This is a post." };
const resp = await axios.post(url, payload);
console.log("Created:", resp.data);
} catch (error) {
console.log("Server error:", error.response?.data);
}
};
createPost();
Global Defaults (Convenient, But Use Carefully)
Axios lets you set defaults once, so you don’t repeat config everywhere.
import axios from "axios";
axios.defaults.baseURL = "https://api.example.com";
axios.defaults.headers["Accept"] = "application/json";
// Example: attach a token (if you have one)
const AUTH_TOKEN = "YOUR_TOKEN_HERE";
axios.defaults.headers["Authorization"] = `Bearer ${AUTH_TOKEN}`;
// Example: set default content-type for POST requests
axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
Note: In newer Axios versions,
axios.defaults.headers.commonmay not behave like older tutorials. Setting headers viaaxios.defaults.headers[...]is a safer pattern.
Create a Custom Axios Instance (Recommended)
Instead of modifying global defaults, you can create an Axios instance for a specific API.
import axios from "axios";
const authFetch = axios.create({
baseURL: "https://www.course-api.com",
headers: {
Accept: "application/json",
},
});
// Usage
const getProducts = async () => {
const { data } = await authFetch.get("/react-store-products");
console.log(data);
};
getProducts();
This keeps your codebase cleaner—especially when you have multiple APIs or different auth rules.
Interceptors (Power Feature)
Interceptors let you run logic before a request is sent and after a response is received.
Typical uses:
- Automatically attach auth tokens
- Log requests
- Handle global errors like 401/404
authFetch.interceptors.request.use(
(request) => {
request.headers["Accept"] = "application/json";
console.log("Request sent:", request.method?.toUpperCase(), request.url);
return request; // must return the request
},
(error) => Promise.reject(error), // Keep the promise in a rejected state so the caller's catch/try-catch still runs.
);
authFetch.interceptors.response.use(
(response) => {
console.log("Response received:", response.status);
return response;
},
// Error handler for the response interceptor.
// This runs when Axios gets a non-2xx response (like 404/401/500) or a network failure.
// `error.response` (when it exists) contains the server response details: status, headers, data, etc.
(error) => {
console.log("Response error:", error.response);
// Example: handle "Not Found" globally in one place.
// Useful for showing a toast, redirecting to a 404 page, or tracking broken links.
if (error.response?.status === 404) {
console.log('NOT FOUND');
// you could redirect, show toast, etc.
}
// Example: if it's 401 (Unauthorized), you usually log the user out / clear the token.
// if (error.response?.status === 401) logoutUser();
// IMPORTANT:
// `Promise.reject(error)` re-throws the error as a rejected promise.
// That means the calling code still sees this request as "failed" and its `.catch(...)`
// (or try/catch around `await`) will run. Without rejecting, Axios would treat it like a success.
return Promise.reject(error);
}
);
Quick Mental Model: Axios vs Fetch
Why people often prefer Axios:
- ✅
response.datagives you parsed data directly - ✅ Automatic JSON transformation (most cases)
- ✅ Better ergonomics around headers/config
- ✅ Built-in interceptors
- ✅ Cleaner error handling via
error.response
Fetch is still totally valid—Axios just tends to reduce friction in real projects.
Wrap-up
If your upcoming project involves:
- lots of API calls,
- consistent headers/tokens,
- centralized error handling,
…Axios will likely make your life easier. Start simple with axios.get() / axios.post(), then scale into instances and interceptors as the app grows.
Top comments (2)
Common Axios mistakes
response.dataAxios wraps the payload, so you usually want:—not
responseitself.GETvsPOSTargumentsget(url, config)post(url, data, config)A super common bug is accidentally sending headers as the “data” argument inpost().fetch-style error checks Withfetch, you checkres.ok. With Axios, non-2xx responses throw, so handle it incatch:axios.defaults...affects everything. Prefer a custom instance for APIs/auth:headers.commonSome newer Axios setups don’t behave like older examples. Safer pattern:Forgetting to return in interceptors
If you don’t
return request/return response, things break silently.Not cancelling requests on unmount (React)
Can cause “state update on unmounted component” warnings. Use
AbortControlleror your preferred pattern.About point #8 in the previous comment, it’s not just old class-based React — it can happen in both class components and functional components.
Why it happens
When you start a request and the component unmounts before the request finishes, the promise can still resolve later. If you then do something like
setState(...)(class) orsetX(...)(hooks), you’re trying to update UI that no longer exists.this.setState(...)after unmountsetSomething(...)after unmount (fromuseState)Historically this produced the classic warning: “Can’t perform a React state update on an unmounted component.”
(React has evolved and the exact warning behavior depends on version/dev mode, but the underlying issue is still real: wasted work + possible memory leaks + weird UI timing.)
Functional component pattern: AbortController (recommended)
Axios supports aborting via
signal(modern versions):Notes
useEffectruns on unmount, so that’s where you abort.CancelToken— it’s largely legacy compared toAbortController.Alternative: “isMounted” flag (works, but less ideal)
This doesn’t cancel the request, it just prevents state updates:
This avoids setting state after unmount, but the request still runs.
So the answer
✅ It applies to functional components too.
In modern React apps, you’ll mostly hit this with hooks + async calls inside
useEffect, event handlers that trigger requests, or route changes/unmounts.