To use Axios in React, install it with npm install axios, create a configured instance with axios.create(), and make requests inside useEffect (or a data library) with api.get(), api.post() and so on. Axios parses JSON automatically into response.data, rejects the promise on 4xx and 5xx errors, supports interceptors for auth tokens, and can be cancelled with an AbortController when a component unmounts.
This guide covers Axios 1.20 (the current version) with React 19: GET, POST, PATCH and DELETE requests, a reusable instance with interceptors, error handling, timeouts, cancellation, and when plain fetch is enough. I ran every example against a local test API, and the outputs shown are real.
What Is Axios?
Axios is a promise-based HTTP client for the browser and Node.js. In the browser it uses XMLHttpRequest under the hood (newer versions can also use the fetch adapter), and in Node.js it uses the http module, so the same code works on both sides. If HTTP methods, headers and status codes are new to you, read what an HTTP request is first.
Axios vs fetch
Every modern browser and Node.js 18+ now include fetch, so Axios is a convenience rather than a necessity. The practical differences:
| Axios | fetch | |
|---|---|---|
| Install | npm install axios | Built in |
| JSON responses | Parsed automatically into response.data | Call await res.json() |
| Sending JSON | Pass an object; Axios serializes it and sets Content-Type | JSON.stringify and set the header yourself |
| HTTP errors (404, 500) | Rejects the promise | Resolves; you must check res.ok |
| Timeouts | timeout option | AbortSignal.timeout(ms) |
| Interceptors | Built in, for requests and responses | Write a wrapper function |
| Base URL and shared defaults | axios.create() | Write a wrapper function |
| Upload progress | onUploadProgress | Not supported directly |
| Bundle size | Adds a dependency (about 20 KB gzipped when I bundled it with esbuild) | Zero |
Choose Axios when you want interceptors, shared configuration and automatic error handling across many API calls; plain fetch is fine for a handful of requests. Either way, the React patterns are the same as in my guide to fetching data with React hooks.
Install Axios
npm install axiosCommit your lockfile and install with npm ci in CI; see the security note at the end of this post for why.
Create an Axios Instance
Instead of calling axios.get with full URLs everywhere, create one instance with your base URL, timeout and headers, and add interceptors to it:
import axios from 'axios';
// One configured instance for your whole app
export const api = axios.create({
baseURL: 'http://localhost:3000',
timeout: 1000, // fail requests that take longer than 1 s
headers: { Accept: 'application/json' },
});
// Request interceptor: attach the auth token to every request
api.interceptors.request.use((config) => {
const token = getToken();
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Response interceptor: one place to react to errors such as an expired session
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) console.log(' [interceptor] 401 received: redirect to login here');
return Promise.reject(error);
},
);
let token = null;
export const setToken = (t) => { token = t; };
const getToken = () => token;The request interceptor adds the token to every request, so components never handle it. The response interceptor gives you one place to react to an expired session, for example by refreshing the token or sending the user to the login page.
GET, POST, PATCH and DELETE Requests
// GET with query parameters (Axios builds ?q=hello for you)
const { data: found, status } = await api.get('/posts', { params: { q: 'hello' } });
// GET 200 [ { id: 1, title: 'Hello HTTP' } ]
// POST JSON
const created = await api.post('/posts', { title: 'Sent with Axios' });
// POST 201 { id: 2, title: 'Sent with Axios' }
// PATCH (partial update) and DELETE
const patched = await api.patch(`/posts/${created.data.id}`, { title: 'Edited' });
// PATCH 200 { id: 2, title: 'Edited' }
await api.delete(`/posts/${created.data.id}`);
// DELETE 204
// Several requests in parallel
const [all, filtered] = await Promise.all([
api.get('/posts'),
api.get('/posts', { params: { q: 'hello' } }),
]);The response object has data (the parsed body), status, statusText, headers and config. Use put to replace a whole resource, and pass a FormData object to post for file uploads.
Using Axios in a React Component
Make requests in an effect, track loading and error state, and cancel the request in the cleanup function so a slow, stale response can never overwrite a newer one:
import { useEffect, useState } from 'react';
import axios from 'axios';
const api = axios.create({ baseURL: 'http://localhost:3001/api' });
export default function User({ userId }) {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setError(null);
api.get(`/users/${userId}`, { signal: controller.signal })
.then((res) => setUser(res.data)) // res.data is already parsed JSON
.catch((err) => {
if (axios.isCancel(err)) return; // we cancelled it; ignore
setError(err.response ? `HTTP ${err.response.status}` : err.message);
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort(); // cancel if userId changes or we unmount
}, [userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Something went wrong: {error}</p>;
return <p>{user.name}</p>;
}I tested this component in headless Brave against an API where user 1 responds in 800 ms and user 2 in 100 ms. After switching from user 2 to 1 and straight back to 2, it showed the correct “Alan Turing”, and the browser logged the cancelled request for user 1 as net::ERR_ABORTED. For a user that doesn’t exist it showed “Something went wrong: HTTP 404”.
For larger apps, pair Axios with TanStack Query: use your Axios instance inside the query function and pass the signal TanStack Query provides, and you get caching and retries on top. For sharing fetched data across components, see global state with React Redux.
Handling Errors
Axios rejects for three different reasons, and it helps to handle them separately:
try {
await api.get('/nope');
} catch (err) {
if (axios.isCancel(err)) {
// you cancelled it
} else if (err.response) {
// the server answered with 4xx/5xx
console.log(err.response.status, err.response.data); // 404 { error: 'Not found' }
} else if (err.request) {
// no response: network error, CORS failure or timeout
console.log(err.code, err.message);
} else {
// something went wrong setting up the request
}
}Use axios.isAxiosError(err) to check that an error came from Axios (it returned true for the 404 in my test). To treat some statuses as success, set validateStatus, for example validateStatus: (s) => s < 500.
Timeouts and Cancelling Requests
// Timeout: the instance above has timeout: 1000
try {
await api.get('/slow'); // server takes 2 s
} catch (err) {
console.log(err.code, err.message); // ECONNABORTED timeout of 1000ms exceeded
}
// Cancel with AbortController
const controller = new AbortController();
const pending = api.get('/slow', { signal: controller.signal, timeout: 5000 });
controller.abort();
try {
await pending;
} catch (err) {
console.log(axios.isCancel(err), err.name, err.code); // true CanceledError ERR_CANCELED
}The old CancelToken API is deprecated; use AbortController, which is also what fetch uses.
Security Note: Keep Axios Up to Date and Pinned
On 31 March 2026, attackers took over an Axios maintainer’s npm account and published two malicious versions, 1.14.1 and 0.30.4, which installed a remote access trojan through a fake dependency. They were online for about three hours before being removed (Tenable’s FAQ has the details). If either version was ever installed on a machine or CI runner, treat it as compromised and rotate its credentials. To reduce the risk from any package:
- Commit
package-lock.jsonand usenpm ci, so builds install exactly the versions you reviewed. - Update dependencies deliberately (for example with Dependabot or Renovate pull requests), not with blind
npm updatein CI. - Consider
npm config set ignore-scripts truefor CI installs that don’t need install scripts, since that attack ran through an install script. - Check
npm auditand your registry’s security advisories regularly.
Conclusion
Axios makes HTTP requests in React a little shorter and more consistent: one configured instance, automatic JSON handling, errors that actually throw, interceptors for tokens and a timeout option. Use it inside effects with an AbortController cleanup (or inside TanStack Query), handle the three kinds of errors separately, and keep your dependency versions pinned. If you only need a couple of simple requests, the built-in fetch does the job without a dependency.
FAQs
How do I use Axios in React?
Install it with npm install axios, create an instance with axios.create({ baseURL }), and call api.get or api.post inside useEffect or a data library. Store response.data in state, handle errors in catch, and cancel with AbortController in the effect cleanup.
Is Axios better than fetch?
Not better, just more convenient. Axios parses JSON automatically, throws on HTTP errors, and has interceptors, timeouts and shared configuration. fetch is built in and adds no bundle size. Both work well with React.
How do I send a POST request with Axios?
Call api.post(url, data) with a JavaScript object; Axios serializes it to JSON and sets the Content-Type header. The parsed response is in response.data.
How do I add an Authorization header to every Axios request?
Use a request interceptor on your instance: api.interceptors.request.use(config => { config.headers.Authorization = `Bearer ${token}`; return config; }).
How do I cancel an Axios request in React?
Create an AbortController in useEffect, pass controller.signal in the request config, and call controller.abort() in the cleanup function. Check axios.isCancel(err) in your catch block to ignore the cancellation.
Does Axios throw an error on 404?
Yes. By default Axios rejects for any status outside 200–299, with the response available on err.response. You can change this with the validateStatus option.
Which Axios versions were compromised?
Versions 1.14.1 and 0.30.4, published on 31 March 2026 and removed within hours. Use a current version, pin it in your lockfile, and treat any machine that installed those versions as compromised.








