Report this

What is the reason for this report?

How To Use Axios with React

Updated on September 25, 2025
English
How To Use Axios with React

Introduction

Axios simplifies making API requests in React, offering better syntax, built-in error handling, and interceptors compared to the native Fetch API. This makes it a popular choice for developers looking to fetch data with Axios in React. In this React Axios tutorial, you will see practical examples of how to fetch data with Axios in React applications.

Axios is promise-based, which gives you the ability to take advantage of JavaScript’s async and await for more readable asynchronous code.

You can also intercept and cancel requests, and there’s built-in client-side protection against cross-site request forgery.

In this article, you will see examples of how to use Axios to access the popular JSONPlaceholder API within a React application.

Axios vs Fetch: Quick Comparison

Choosing between Axios and the native Fetch API is a common decision point for React developers. This section provides a side-by-side comparison to highlight why many teams prefer Axios in production environments, while also outlining cases where Fetch may be sufficient for smaller projects.

Feature Axios Fetch API
Syntax Axios offers a clean, promise-based syntax that significantly reduces boilerplate code. Developers can quickly chain requests, handle responses, and set global configurations. This streamlined approach makes it easier for teams to maintain consistent API handling across medium and large-scale React applications. Fetch provides a low-level interface with more verbose syntax. Developers must manually configure headers, parse JSON, and check status codes for every request. While it’s built into the browser and requires no installation, it often leads to repetitive code in larger React projects.
Error Handling Axios automatically throws errors for HTTP response codes outside the 2xx range. It provides detailed error objects with response, request, and message properties, making debugging easier. Built-in error handling ensures developers can quickly identify and fix issues without writing repetitive conditional checks. Fetch only rejects a promise for network-level failures. HTTP errors like 404 or 500 are treated as resolved promises, requiring manual status checks. Developers must explicitly throw errors based on response.ok, leading to more boilerplate and increased chances of inconsistent error handling in applications.
Interceptors Axios includes request and response interceptors, enabling developers to inject authentication tokens, log activity, or transform data globally before reaching components. This feature is particularly valuable in enterprise applications where centralized request management improves security, scalability, and developer productivity across teams. Fetch does not provide built-in interceptor functionality. To achieve similar outcomes, developers must wrap Fetch in custom utility functions or middleware. This increases complexity, reduces maintainability, and makes centralized request transformations or authentication token injection more challenging in large-scale React applications.
JSON Handling Axios automatically transforms JSON responses, eliminating the need for manual .json() parsing. This simplifies data handling and minimizes developer error, allowing teams to focus on application logic rather than repetitive parsing. Automatic JSON handling makes Axios especially attractive in data-heavy React applications. Fetch requires developers to manually parse JSON using .json(). This additional step increases boilerplate and introduces the risk of errors if developers forget to parse or incorrectly chain promises. While straightforward for small apps, it becomes repetitive and error-prone in larger React projects.
Request Cancellation Axios supports request cancellation natively through the CancelToken API and the newer AbortController integration. This is essential for React apps with dynamic UI states where requests may need to be aborted, such as live search or component unmounts, improving performance and user experience. Fetch supports cancellation through the AbortController API. However, it requires manual setup and integration, making it less intuitive than Axios. Developers often need to write additional code to manage cancellations effectively, which can be cumbersome in complex React applications handling multiple parallel requests.

Key Takeaways

  • Axios offers a superior developer experience compared to Fetch: Axios provides a cleaner, promise-based syntax that reduces boilerplate, automatically parses JSON responses, and includes built-in error handling for HTTP status codes. This makes it ideal for medium and large React applications. In contrast, the native Fetch API is lightweight and built-in, but requires manual status checks, JSON parsing, and lacks features like interceptors, making it better suited for small utilities or simple use cases.

  • Comprehensive request patterns with real-world examples: This guide walks through making GET, POST, and DELETE requests using both React class components and hooks. Each example demonstrates how to handle loading and error states, update UI responsively, and cancel in-flight requests to prevent memory leaks or unwanted state updates after unmounting.

  • Centralized configuration and interceptors for authentication and telemetry: By creating a shared Axios instance and registering request and response interceptors, you can automatically attach authentication tokens (such as Authorization: Bearer <token>), add correlation IDs for tracing, and normalize error handling across your app. This ensures consistent security, observability, and error reporting, and allows for advanced flows like automatic token refresh and retry on 401 errors.

  • Async/await for readable and maintainable code: Leveraging async/await with Axios keeps asynchronous logic top-to-bottom and makes error handling explicit with try/catch blocks. This approach integrates naturally with React hooks like useEffect and useState, resulting in more readable, maintainable, and bug-resistant data fetching code.

  • Robust error handling for resilient user experiences: The article details how to branch on err.response (HTTP errors), err.request (network errors), and err.message (setup/timeouts) to provide user-friendly UI messages, log diagnostic details for developers, and implement retries or alternative flows. Centralizing error handling ensures that users see clear, actionable feedback and that sensitive information is never exposed in the UI.

  • Advanced Axios patterns for real-world requirements: Beyond basic CRUD, Axios supports advanced use cases such as paginated data fetching with query parameters, concurrent requests using Promise.all, file uploads with progress tracking via onUploadProgress, and custom timeout handling. These features enable you to build enterprise-grade React applications that are scalable, performant, and user-friendly.

  • Best practices for maintainable React apps: The guide emphasizes separating API logic from UI components, creating reusable hooks (like useAxios), and always canceling requests on unmount. It also covers when to choose Axios over Fetch, how to type responses and errors in TypeScript, and how to avoid common security pitfalls such as exposing tokens or stack traces in the UI.

Need to deploy a React project and have it live? Check out DigitalOcean App Platform and deploy a React project directly from GitHub in minutes.

Prerequisites

To follow along with this article, you’ll need the following:

  • Node.js version 20.x (latest LTS) installed on your computer. To install this on macOS or Ubuntu, follow the steps in Install Node.js on macOS or Install Node.js on Ubuntu.
  • A new React project set up with Create React App or using the official React documentation for starting projects. This article uses React version 18.x, the latest stable release.
  • Axios installed via npm (npm install axios) — this article demonstrates how to install and use it in React projects. The examples here are tested with Axios version 1.x. See the Axios GitHub repository for documentation and updates.
  • npm v10.x (shipped with Node.js 20.x) or Yarn as your package manager.
  • A basic understanding of JavaScript, which you can build with the How To Code in JavaScript series, along with foundational knowledge of HTML and CSS.

This tutorial was verified with Node.js v20.11.1, npm v10.2.4, react v18.2.0, and axios v1.6.x.

Step 1 — Adding Axios to the Project

Now, you will learn how to install Axios in React and add it to a project you created following the How to Set up a React Project with Create React App tutorial.

  1. npx create-react-app react-axios-example

To add Axios to the project, open your terminal and change directories into your project:

  1. cd react-axios-example

Then run this command to install Axios:

  1. npm install axios
  1. yarn add axios

Both npm and Yarn will install the latest stable version of Axios, ensuring your project uses the most up-to-date features and security fixes.

At a Glance — Step 1 (Install Axios)

  • Goal: Install Axios and verify it’s available to your app.
  • Run: npm install axios (or yarn add axios).
  • Verify:
    1. npm list axios → shows a version like axios@1.x.
    2. cat package.jsondependencies contains "axios": "^1.x".
    3. Add import axios from 'axios' to a file; the dev server should compile without errors.

Expected Output

$ npm list axios
project@1.0.0 /path/to/project
└── axios@1.6.x

Implementation Checklist (GET Request)

  • [ ] Installed Axios with npm/yarn
  • [ ] Verified in package.json
  • [ ] Able to import axios without a build error

Step 2 — Axios GET Request React Example

In this example, you create a new component and import Axios into it to send a GET request.

Inside your React project, you will need to create a new component named PersonList.

First, create a new components subdirectory in the src directory:

  1. mkdir src/components

In this directory, create PersonList.js and add the following code to the component:

src/components/PersonList.js
import React from 'react';
import axios from 'axios';

export default class PersonList extends React.Component {
  state = {
    persons: []
  }

  componentDidMount() {
    axios.get(`https://jsonplaceholder.typicode.com/users`)
      .then(res => {
        const persons = res.data;
        this.setState({ persons });
      })
      .catch(err => {
        console.error('Error fetching data:', err);
      });
  }

  render() {
    return (
      <ul>
        {
          this.state.persons
            .map(person =>
              <li key={person.id}>{person.name}</li>
            )
        }
      </ul>
    )
  }
}

First, you import React and Axios so that both can be used in the component. Then you hook into the componentDidMount lifecycle hook and perform a GET request.

You use axios.get(url) with a URL from an API endpoint to get a promise which returns a response object. Inside the response object, there is data that is then assigned the value of person.

Axios supports both the .then() promise style and the modern async/await syntax. While .then() is straightforward and works well in lifecycle methods, using async/await can make the code more readable in functional components with hooks.

You can also get other information about the request, such as the status code under res.status or more information inside of res.request.

Functional Component with Hooks (Async/Await)

Modern React apps favor functional components and hooks. The example below shows a production‑ready pattern with loading and error states, request cancellation via AbortController (supported by Axios), and clear separation of concerns. This approach improves UX, avoids setting state on unmounted components, and aligns with enterprise best practices.

src/components/PersonListHooks.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';

function PersonListHooks() {
  const [persons, setPersons] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController(); // Axios supports AbortController
    const fetchData = async () => {
      setLoading(true);
      setError(null);
      try {
        const res = await axios.get('https://jsonplaceholder.typicode.com/users', {
          signal: controller.signal,
          // headers: { Authorization: `Bearer ${token}` }, // example for auth
        });
        setPersons(res.data);
      } catch (err) {
        // Distinguish between cancellation, network errors, and HTTP errors
        if (axios.isCancel?.(err) || err.name === 'CanceledError') return;
        if (err.response) {
          // Server responded with a non-2xx status
          setError(`Server error: ${err.response.status} ${err.response.statusText}`);
        } else if (err.request) {
          // No response received
          setError('Network error: no response from server');
        } else {
          // Something else happened while setting up the request
          setError(`Request error: ${err.message}`);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();
    return () => controller.abort(); // Cleanup: cancel in-flight request on unmount
  }, []); // Empty dependency array: run once on mount

  if (loading) return <p>Loading users…</p>;
  if (error) return <p role="alert">{error}</p>;

  return (
    <ul>
      {persons.map((person) => (
        <li key={person.id}>{person.name}</li>
      ))}
    </ul>
  );
}

export default PersonListHooks;

Why this pattern works (EEAT & best practices):

  • Async/await readability: Reduces promise chaining complexity and improves maintainability for teams.
  • Explicit states: loading and error provide accessible UX and clearer logic for retries and skeleton UIs.
  • Request cancellation: AbortController prevents race conditions and memory leaks when a component unmounts or dependencies change.
  • Robust error triage: Differentiating err.response, err.request, and other errors leads to actionable logs and safer user messaging.
  • Scalability: This code drops into larger apps where you’ll later add an Axios instance, interceptors (auth/telemetry), and retries with backoff.

Hooks vs Class Components — when to choose what

  • Prefer hooks for new code: simpler state/effect model, easier composition via custom hooks, and no this/lifecycle pitfalls.
  • Keep class components when maintaining legacy codebases or where class-only patterns exist (for example, established Error Boundaries) until a planned refactor.
  • Mixed apps are fine: adopt hooks incrementally in leaf components, then lift common logic into reusable hooks as you modernize.

Add this component to your app.js:

src/app.js
import PersonList from './components/PersonList.js';

function App() {
  return (
    <div className="App">
      <PersonList/>
    </div>
  )
}

Alternative (Hooks): Use the hooks version of the list component.

src/app.js
import PersonListHooks from './components/PersonListHooks';

function App() {
  return (
    <div className="App">
      <PersonListHooks />
    </div>
  );
}

Then run your application:

  1. npm start

View the application in the browser. You will be presented with a list of 10 names.

At a Glance — Step 2 (GET /users)

  • Goal: Render users from JSONPlaceholder.
  • Paste: src/components/PersonListHooks.js, import into App.
  • Run: npm start.
  • You should see: an unordered list of 10 names in the DOM.

Expected Output (DOM excerpt)

<ul>
  <li>Leanne Graham</li>
  <li>Ervin Howell</li>
  <li>Clementine Bauch</li>
  <!-- … 7 more … -->
</ul>

Implementation Checklist (POST Request)

  • [ ] Component file created and exported
  • [ ] Imported into App and rendered
  • [ ] No console errors; list of 10 names appears

Step 3 — Axios POST Request React Example

In this step, you will use Axios with another HTTP request method called POST.

Below is an updated example of the PersonAdd component, now using async/await, logging the HTTP status, and robust error handling:

src/components/PersonAdd.js
import React from 'react';
import axios from 'axios';

export default class PersonAdd extends React.Component {
  state = {
    name: ''
  }

  handleChange = event => {
    this.setState({ name: event.target.value });
  }

  handleSubmit = async event => {
    event.preventDefault();

    const user = { name: this.state.name };

    try {
      const res = await axios.post('https://jsonplaceholder.typicode.com/users', user);
      console.log('Status:', res.status);
      console.log('Response data:', res.data);
    } catch (err) {
      if (err.response) {
        console.error('POST failed with status:', err.response.status, err.response.statusText);
      } else if (err.request) {
        console.error('Network error: no response from server');
      } else {
        console.error('Request setup error:', err.message);
      }
    }
  }

  render() {
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <label>
            Person Name:
            <input type="text" name="name" onChange={this.handleChange} />
          </label>
          <button type="submit">Add</button>
        </form>
      </div>
    )
  }
}

This example uses async/await for clarity, logs res.status for visibility into the HTTP result, and includes try/catch branches that distinguish between server errors (err.response), network timeouts (err.request), and request setup issues (err.message).

Inside the handleSubmit function, you prevent the default action of the form. Then update the state to the user input.

Using POST gives you the same response object with information that you can use inside of a then call.

To complete the POST request, you first capture the user input. Then you add the input along with the POST request, which will give you a response. You can then console.log the response, which should show the user input in the form.

Add this component to your app.js:

src/app.js
import PersonList from './components/PersonList';
import PersonAdd from './components/PersonAdd';

function App() {
  return (
    <div className="App">
      <PersonAdd/>
      <PersonList/>
    </div>
  )
}

Alternative (Hooks): Swap in the hooks‑based POST component.

src/app.js
import PersonAddHooks from './components/PersonAddHooks';

function App() {
  return (
    <div className="App">
      <PersonAddHooks />
    </div>
  );
}

Then run your application:

  1. npm start

View the application in the browser. You will be presented with a form for submitting new users. Check the console after submitting a new user.

At a Glance — Step 3 (POST /users)

  • Goal: Submit a new user and log the result.
  • Paste: src/components/PersonAddHooks.js, import into App.
  • Run: Type a name → Add.
  • You should see: a 201 or 200 status in console (JSONPlaceholder mocks creation).

Expected Output for Console excerpt

Status: 201
Response data: { id: 101, name: "Ada Lovelace" }

Implementation Checklist (DELETE Request)

  • [ ] Input updates local state
  • [ ] Submit triggers axios.post
  • [ ] Console shows HTTP status and response JSON
  • [ ] Errors display a friendly message (if any)

Functional Component POST with Hooks (Async/Await)

The hooks version below mirrors the class example but adds production-friendly details: loading state, error messages, HTTP status logging, and request cancellation with AbortController. This keeps the UI responsive and prevents state updates after unmount.

src/components/PersonAddHooks.js
import React, { useState, useEffect, useRef } from 'react';
import axios from 'axios';

function PersonAddHooks() {
  const [name, setName] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const lastStatus = useRef(null);

  useEffect(() => {
    // No initial POST here; controller is created per submission
    return () => {
      // Cleanup if needed
    };
  }, []);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    const controller = new AbortController();

    try {
      const res = await axios.post(
        'https://jsonplaceholder.typicode.com/users',
        { name },
        { signal: controller.signal }
      );
      lastStatus.current = res.status;
      console.log('Status:', res.status);
      console.log('Response data:', res.data);
      setName(''); // reset on success
    } catch (err) {
      if (axios.isCancel?.(err) || err.name === 'CanceledError') return;
      if (err.response) {
        setError(`POST failed: ${err.response.status} ${err.response.statusText}`);
      } else if (err.request) {
        setError('Network error: no response from server');
      } else {
        setError(`Request setup error: ${err.message}`);
      }
    } finally {
      setLoading(false);
    }

    // Optional: return a function to cancel if you convert this to a long-running op
    // return () => controller.abort();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Person Name:
        <input
          type="text"
          name="name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          disabled={loading}
        />
      </label>
      <button type="submit" disabled={loading || !name.trim()}>
        {loading ? 'Adding…' : 'Add'}
      </button>
      {error && <p role="alert">{error}</p>}
      {lastStatus.current && <p>Last status: {lastStatus.current}</p>}
    </form>
  );
}

export default PersonAddHooks;

Tip: Disable the submit button while loading to prevent duplicate requests. For authenticated APIs, move POST calls to a shared Axios instance with request interceptors for tokens.

Step 4 — Axios DELETE Request React Example

In this example, you will see how to delete items from an API using axios.delete and passing a URL as a parameter.

Inside your React project, you will need to create a new component named PersonRemove.

Replace the PersonRemove.js file with the following version, which uses async/await and robust error handling:

src/PersonRemove.js
import React from 'react';
import axios from 'axios';

export default class PersonRemove extends React.Component {
  state = {
    id: ''
  }

  handleChange = event => {
    this.setState({ id: event.target.value });
  }

  handleSubmit = async event => {
    event.preventDefault();
    const { id } = this.state;
    if (!id) return;

    try {
      // Note: some production APIs require auth headers; see note below
      const res = await axios.delete(`https://jsonplaceholder.typicode.com/users/${id}` /*, {
        headers: { Authorization: `Bearer <token>` }
      }*/);
      console.log('Status:', res.status);
      console.log('Response data:', res.data);
    } catch (err) {
      if (err.response) {
        console.error('DELETE failed with status:', err.response.status, err.response.statusText);
      } else if (err.request) {
        console.error('Network error: no response from server');
      } else {
        console.error('Request setup error:', err.message);
      }
    }
  }

  render() {
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <label>
            Person ID:
            <input type="number" name="id" onChange={this.handleChange} />
          </label>
          <button type="submit">Delete</button>
        </form>
      </div>
    )
  }
}

Note: API responses vary. In production systems, DELETE endpoints may require headers (for example, Authorization bearer tokens or CSRF tokens) or additional parameters. For larger apps, prefer a shared Axios instance with request/response interceptors (see the Interceptors section) to inject auth and telemetry consistently.

Functional Component DELETE with Hooks (Async/Await)

The hooks version mirrors the class example and adds loading/error states, status logging, and request cancellation. This prevents race conditions and keeps UI feedback responsive.

src/components/PersonRemoveHooks.js
import React, { useState, useRef } from 'react';
import axios from 'axios';

function PersonRemoveHooks() {
  const [id, setId] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const lastStatus = useRef(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!id.trim()) return;
    setLoading(true);
    setError(null);
    const controller = new AbortController();

    try {
      // Some APIs require headers (auth/CSRF). Add via Axios instance or here.
      const res = await axios.delete(
        `https://jsonplaceholder.typicode.com/users/${id}`,
        { signal: controller.signal }
      );
      lastStatus.current = res.status;
      console.log('Status:', res.status);
      console.log('Response data:', res.data);
      setId('');
    } catch (err) {
      if (axios.isCancel?.(err) || err.name === 'CanceledError') return;
      if (err.response) {
        setError(`DELETE failed: ${err.response.status} ${err.response.statusText}`);
      } else if (err.request) {
        setError('Network error: no response from server');
      } else {
        setError(`Request setup error: ${err.message}`);
      }
    } finally {
      setLoading(false);
    }

    // Optional: return () => controller.abort(); // if you adapt to a long-running flow
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Person ID:
        <input
          type="number"
          name="id"
          value={id}
          onChange={(e) => setId(e.target.value)}
          disabled={loading}
        />
      </label>
      <button type="submit" disabled={loading || !id.trim()}>
        {loading ? 'Deleting…' : 'Delete'}
      </button>
      {error && <p role="alert">{error}</p>}
      {lastStatus.current && <p>Last status: {lastStatus.current}</p>}
    </form>
  );
}

export default PersonRemoveHooks;

Tip: For authenticated APIs, prefer a shared Axios instance with request interceptors to inject Authorization headers and response interceptors to handle 401 refresh flows.

src/app.js
import PersonList from './components/PersonList';
import PersonAdd from './components/PersonAdd';
import PersonRemove from './components/PersonRemove';

function App() {
  return (
    <div className="App">
      <PersonAdd/>
      <PersonList/>
      <PersonRemove/>
    </div>
  )
}

Alternative (Hooks): Use the hooks‑based delete component.

src/app.js
import PersonRemoveHooks from './components/PersonRemoveHooks';

function App() {
  return (
    <div className="App">
      <PersonRemoveHooks />
    </div>
  );
}

Then run your application:

  1. npm start

View the application in the browser. You will be presented with a form for removing users.

At a Glance — Step 4 (DELETE /users/:id)

  • Goal: Delete a user by ID and confirm the outcome.
  • Paste: src/components/PersonRemoveHooks.js, import into App.
  • Run: Enter 1Delete.
  • You should see: Status: 200 and {} in console (JSONPlaceholder returns an empty object).

Expected Output (Console excerpt)

Status: 200
Response data: {}

Implementation Checklist (Axios Instance & Interceptors)

  • [ ] Input captured and validated (non-empty)
  • [ ] axios.delete called with correct path
  • [ ] Console shows HTTP status and response body
  • [ ] Errors display a friendly message (if any)

Step 5 — Creating an Axios Instance & Using Interceptors

A shared Axios instance centralizes configuration (base URL, headers, timeouts) and enables interceptors for auth and telemetry. This improves consistency and reduces boilerplate.

Create the instance

// src/api.js
import axios from 'axios';

const API = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com/',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
  withCredentials: false, // set true only if your API uses cookies
});

export default API;

Add a request interceptor (auth headers, correlation IDs)

// src/api.interceptors.js
import API from './api';

API.interceptors.request.use(
  (config) => {
    // Example: attach auth token from storage
    const token = localStorage.getItem('access_token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    // Example: add a correlation ID for tracing
    config.headers['X-Request-ID'] = crypto.randomUUID?.() || Date.now().toString(36);
    return config;
  },
  (error) => Promise.reject(error)
);

Add a response interceptor (401 handling, basic telemetry)

// src/api.interceptors.js (continued)
API.interceptors.response.use(
  (response) => response,
  async (error) => {
    const { response, config } = error;
    // Basic telemetry
    console.warn('API error:', {
      url: config?.url,
      method: config?.method,
      status: response?.status,
    });

    // Example: handle expired access token
    if (response?.status === 401 && !config.__isRetry) {
      config.__isRetry = true;
      try {
        // Pseudo refresh flow; replace with your auth endpoint
        const refreshToken = localStorage.getItem('refresh_token');
        if (refreshToken) {
          // await axios.post('/auth/refresh', { refreshToken });
          // localStorage.setItem('access_token', newAccessToken);
          // config.headers.Authorization = `Bearer ${newAccessToken}`;
          return API(config); // retry original request
        }
      } catch (e) {
        // fall through to reject
      }
    }
    return Promise.reject(error);
  }
);

Use the instance in components

// src/components/PersonRemove.js
import React from 'react';
import API from '../api';
import '../api.interceptors'; // ensure interceptors are registered once

export default class PersonRemove extends React.Component {
  state = { id: '' };

  handleChange = (e) => this.setState({ id: e.target.value });

  handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const res = await API.delete(`users/${this.state.id}`);
      console.log(res.data);
    } catch (err) {
      console.error('Delete failed:', err);
    }
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Person ID:
          <input type="number" name="id" onChange={this.handleChange} />
        </label>
        <button type="submit">Delete</button>
      </form>
    );
  }
}

Security note: Never hard-code secrets in frontend code. Store tokens securely and prefer a backend proxy for sensitive operations.

Step 6 — Can I use Axios with async/await in React?

Using async/await with Axios makes code cleaner and easier to reason about, especially in modern React apps that favor hooks. Instead of chaining .then(), you can wrap calls in a try/catch for clearer error handling.

src/components/PersonRemove.js
import React from 'react';
import API from '../api';

export default class PersonRemove extends React.Component {
  state = { id: '' };

  handleChange = (e) => this.setState({ id: e.target.value });

  handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await API.delete(`users/${this.state.id}`);
      console.log('Status:', response.status);
      console.log('Response data:', response.data);
    } catch (err) {
      if (err.response) {
        console.error('Delete failed with status:', err.response.status, err.response.statusText);
      } else if (err.request) {
        console.error('Network error: no response from server');
      } else {
        console.error('Request setup error:', err.message);
      }
    }
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Person ID:
          <input type="number" name="id" onChange={this.handleChange} />
        </label>
        <button type="submit">Delete</button>
      </form>
    );
  }
}

Why async/await in React?
Using async/await improves readability by keeping logic top‑to‑bottom rather than deeply nested in .then() chains. This makes error handling with try/catch explicit and pairs naturally with hooks (useEffect, useState) in modern functional components.

How to Handle Errors with Axios in React

Effective error handling is the difference between a resilient React app and a brittle one. This section explains how to handle errors with Axios in React, what the Axios error object contains, and how to produce user‑friendly messages while keeping detailed logs for developers. (Keywords: Axios error handling React, Axios interceptors React, network error, HTTP error).

The Axios Error Object: Deep Dive and Best Practices

When working with Axios in React, understanding the structure of the error object is crucial for building robust, user-friendly applications. Axios enhances the standard JavaScript Error object with additional properties that provide detailed context about what went wrong during an HTTP request. This enables you to distinguish between different failure scenarios—such as server errors, network issues, and request misconfigurations—and respond appropriately in your UI and logs.

Anatomy of an Axios Error

Field When It Exists What It Contains How to Use It in React Apps
err.response The server responded, but with a non-2xx status An object: { status, statusText, data, headers, config, request } Display status-aware messages (e.g., “Unauthorized” for 401, “Not Found” for 404, “Server Error” for 5xx).
err.request Request was sent but no response received The underlying request object (e.g., XMLHttpRequest in browsers, http.ClientRequest in Node.js) Treat as a network error; prompt the user to check their connection or retry.
err.message Always present A human-readable string describing the error (e.g., timeouts, cancellations, misconfigurations) Show a generic error message, log details for debugging, and confirm if the error was due to cancellation.
err.code Sometimes (e.g., timeouts, network errors) A short error code string (e.g., 'ECONNABORTED' for timeouts) Use for advanced error handling, such as retrying on timeouts or showing specific UI for certain error codes.
err.isAxiosError Always present (Axios >= 0.19.0) Boolean flag (true if error originated from Axios) Safely distinguish Axios errors from other thrown errors in your app or error boundaries.
err.config Always present The Axios config object used for the request Useful for debugging or for retrying the request with modified parameters.

Practical Error Handling Scenarios

  • Authentication/Authorization Errors (401/403):
    Use err.response.status to detect when a user is not authenticated or lacks permission. Prompt for login or show an access denied message.

  • Resource Not Found (404):
    If err.response.status === 404, inform the user that the requested resource does not exist, rather than showing a generic error.

  • Server Errors (5xx):
    For err.response.status >= 500, consider showing a “Server is temporarily unavailable” message and optionally implement retry logic.

  • Network Failures:
    If err.request exists but err.response does not, the request was made but no response was received. This often indicates a network issue or that the server is down. Suggest the user check their connection or try again later.

  • Timeouts and Cancellations:
    If err.code === 'ECONNABORTED' or err.message includes “timeout”, inform the user that the request took too long. If the error was due to cancellation (e.g., component unmount), you may want to silently ignore it.

Quick Checks: Cancellation, Timeouts, and Axios Guards

Use these small, reliable checks to classify common error cases and avoid noisy logs:

import axios /*, { AxiosError }*/ from 'axios';

try {
  // ...
} catch (err) {
  // 1) Narrow to Axios errors (guards against unrelated exceptions)
  if (axios.isAxiosError?.(err)) {
    // 2) Cancellation (component unmounted / user navigated)
    if (err.name === 'CanceledError') {
      // Silently ignore or debug-log only
      return;
    }
    // 3) Timeout (Axios sets code ECONNABORTED on timeout)
    if (err.code === 'ECONNABORTED' || err.message?.toLowerCase().includes('timeout')) {
      // Optionally surface "Request timed out" and suggest retry
    }
    // 4) Network vs HTTP status
    if (err.response) {
      // HTTP error: use err.response.status / statusText
    } else if (err.request) {
      // Network error: no response from server
    }
  } else {
    // Non-Axios error (runtime/logic) — rethrow or handle separately
    throw err;
  }
}

Environment gotchas (browsers):

  • CORS failures often appear as network errors (err.request without err.response). Confirm server CORS headers and preflight handling.
  • Ad‑blockers / extensions can block requests and mimic network errors. Repro in a clean profile to validate.
  • Mixed content (HTTP→HTTPS) and Service Worker misconfigurations can cause silent failures. Check DevTools Network and Application tabs.

Example: Axios Error Object in Practice

Minimal Pattern (Async/Await + Try/Catch)

import axios from 'axios';

try {
  const res = await axios.get('/users');
  // use res.data
} catch (err) {
  if (err.response) {
    console.error('Server error:', err.response.status);
  } else if (err.request) {
    console.error('Network error:', err.message);
  } else {
    console.error('Request setup error:', err.message);
  }
}

Production Pattern (Hooks + Loading/Error + Cancellation)

import React, { useEffect, useState } from 'react';
import axios from 'axios';

export function useUsers() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const run = async () => {
      setLoading(true);
      setError(null);
      try {
        const res = await axios.get('/users', { signal: controller.signal });
        setData(res.data);
      } catch (err) {
        if (axios.isCancel?.(err) || err.name === 'CanceledError') return;
        if (err.response) {
          const { status, statusText } = err.response;
          setError(`Request failed (${status} ${statusText}). Please try again.`);
        } else if (err.request) {
          setError('Network error: no response from server.');
        } else {
          setError(`Request error: ${err.message}`);
        }
      } finally {
        setLoading(false);
      }
    };
    run();
    return () => controller.abort();
  }, []);

  return { data, loading, error };
}

Centralized Handling with an Axios Instance + Interceptors

Use a shared instance to normalize errors and attach context (auth, correlation IDs). This keeps component code clean and messages consistent.

// api.js
import axios from 'axios';
const API = axios.create({ baseURL: '/api', timeout: 10000 });
export default API;
// api.errors.js — normalize messages for UI and keep diagnostics for logs
export function normalizeAxiosError(err) {
  if (err.response) {
    const { status, statusText, data } = err.response;
    const ui = status === 401
      ? 'Please log in to continue.'
      : status === 403
      ? 'You do not have permission to perform this action.'
      : status === 404
      ? 'The requested resource was not found.'
      : status >= 500
      ? 'The server is unavailable right now. Please try again.'
      : `Request failed (${status} ${statusText}).`;
    return { ui, diag: { status, statusText, data } };
  }
  if (err.request) return { ui: 'Network error: no response from server.', diag: { message: err.message } };
  return { ui: `Request error: ${err.message}`, diag: { message: err.message } };
}
// api.interceptors.js — add context; optionally map errors here
import API from './api';
import { normalizeAxiosError } from './api.errors';

API.interceptors.request.use((config) => {
  const token = localStorage.getItem('access_token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  config.headers['X-Request-ID'] = crypto.randomUUID?.() || Date.now().toString(36);
  return config;
});

API.interceptors.response.use(
  (res) => res,
  (error) => {
    // Attach normalized messages for consumers
    error.normalized = normalizeAxiosError(error);
    // Basic telemetry
    console.warn('API error', {
      url: error.config?.url,
      method: error.config?.method,
      ...error.normalized.diag,
    });
    return Promise.reject(error);
  }
);
// Example consumer (component/service)
import API from './api';
import './api.interceptors';

async function deleteUser(id, setError) {
  try {
    await API.delete(`/users/${id}`);
  } catch (err) {
    setError(err.normalized?.ui ?? 'Something went wrong.');
  }
}

UX Guidelines (Reader‑First)

  • Status‑aware copy: 401 → “Please log in,” 403 → “No permission,” 404 → “Not found,” 5xx → “Try again soon.” Avoid leaking internal details in UI.
  • Accessible alerts: Use role attributes (role="alert") for error messages; keep messages concise and actionable.
  • Retries: Only for idempotent GET/PUT and transient network/5xx errors. Use exponential backoff and cap attempts.
  • Cancellation: Always cancel in‑flight requests on unmount to prevent state updates after unmount and wasted bandwidth.
  • Observability: Log status, statusText, a correlation ID, and minimal payload context; avoid PII in logs.
  • Security: Never expose tokens or stack traces in the UI; prefer a backend proxy for sensitive operations.

Quick Reference: Status → Message Mapping

Status class Typical meaning Suggested UI message
4xx Client/auth errors Check credentials/permissions; review your request.
401 Unauthenticated Please log in to continue.
403 Unauthorized/forbidden You do not have permission to perform this action.
404 Resource not found The requested resource was not found.
408/429 Timeout / rate limited Too many requests or timeout; try again later.
5xx Server/temporary outage Server unavailable. Please try again shortly.

TypeScript tips — strongly typed Axios errors

  • Narrow errors safely: In catch (err: unknown), use axios.isAxiosError<ApiError>(err) to guard before reading Axios-specific fields.
  • Type your responses: Prefer axios.get<User[]>('/users') so res.data is correctly typed.
  • Model API error payloads: Create an ApiError interface to surface server messages without any.
import axios, { AxiosError } from 'axios';

interface User { id: number; name: string }
interface ApiError { message: string; code?: string }

async function loadUsers() {
  try {
    const res = await axios.get<User[]>('/users');
    return res.data; // typed: User[]
  } catch (err: unknown) {
    if (axios.isAxiosError<ApiError>(err)) {
      const status = err.response?.status;
      const serverMsg = err.response?.data?.message;
      // Present a friendly UI message; log diagnostics
      throw new Error(serverMsg ?? `Request failed${status ? ` (${status})` : ''}`);
    }
    // Non-Axios/runtime error — rethrow for error boundary/telemetry
    throw err;
  }
}

Bottom line: Centralize Axios error handling for consistency, present user‑friendly messages, and keep diagnostic detail in logs. This balances developer velocity with a trustworthy user experience.

Best Practices for Using Axios in React

Axios vs React Query: When to Use Each

While Axios handles HTTP requests directly, React Query (now TanStack Query) abstracts data fetching, caching, and background updates for complex React apps.

  • Axios: Great for direct, one-off API calls, setting headers, and handling low-level request/response logic.
  • React Query: Ideal when you need caching, auto refetching, and query state management (loading, error, success).
  • Best Practice: Use Axios as the fetcher inside React Query if you want both Axios’ interceptors and React Query’s data-layer power.

Example — Using Axios with React Query

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

const fetchUsers = async () => {
  const { data } = await axios.get("/api/users");
  return data;
};

function UserList() {
  const { data, error, isLoading } = useQuery(["users"], fetchUsers);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

This pattern gives you the best of both worlds: Axios’ request power and React Query’s caching + reactivity.

To build scalable, maintainable, and secure React applications, follow these best practices when working with Axios.

1. Use Axios Interceptors for Authentication and Telemetry

Interceptors allow you to attach authentication tokens (for example, JWT or OAuth2) and correlation IDs to every request. You can also log responses or normalize errors in one place, ensuring consistent behavior across the app.

2. Create Custom Hooks (e.g., useAxios) for Reusability

Encapsulate Axios logic in custom hooks to simplify your components. A useAxios hook can handle loading, error, and retry states while returning data. This promotes cleaner components and allows you to reuse request logic throughout the project.

3. Separate API Logic from Components

Keep your React components focused on UI. Place Axios calls in dedicated API modules (e.g., api.js) or hooks, then import them into your components. This separation improves testability and makes it easier to swap out API implementations.

4. Axios vs Fetch — Quick Reference and In-Depth Comparison

When choosing between Axios and the native Fetch API for making HTTP requests in React (or any JavaScript project), it’s important to consider your application’s complexity, maintainability needs, and developer experience.

Axios is a popular third-party library that simplifies HTTP requests with a concise syntax, automatic JSON parsing, and powerful features like interceptors, request cancellation, and robust error handling. Fetch is a modern, built-in browser API that provides a low-level interface for making HTTP requests, but requires more manual work for common tasks.

When to Use Axios

  • Larger or enterprise-scale applications where you need:
    • Consistent error handling across the app
    • Automatic JSON parsing and transformation
    • Request/response interceptors for authentication, logging, or telemetry
    • Built-in support for request cancellation (with AbortController)
    • Simpler syntax for common use cases (e.g., posting JSON, handling timeouts)
    • Support for older browsers (with polyfills)

When to Use Fetch

  • Smaller projects, quick prototypes, or when bundle size is critical
  • When you want to avoid external dependencies
  • If you only need basic GET/POST requests and are comfortable handling JSON parsing and errors manually

Feature-by-Feature Comparison

Feature Axios Fetch API
Syntax Cleaner, promise-based with less boilerplate More verbose, requires manual JSON parsing
Error Handling Auto throws on non-2xx responses Must manually check response.ok
Interceptors Built-in request/response interceptors Not available, requires custom wrappers
JSON Handling Auto-parses JSON Requires explicit res.json()
Request Cancellation Built-in support (AbortController & cancel tokens) AbortController only, manual integration
Timeout Support Native support via config Requires manual implementation
Progress Events Supported (browser, Node) Not natively supported
Uploads/Downloads Easier with Axios More manual with Fetch
Browser Support Works in all major browsers (with polyfill) Modern browsers only

Tip: For enterprise-scale apps, Axios saves development time with interceptors and error handling. For small apps or when bundle size matters, Fetch may be sufficient.

Reusable Custom Hook: useAxios (with API Instance)

App Integration Examples

Integrating your API components into the main application is essential for building a practical, interactive React app. Below, you’ll see how to combine the PersonList, PersonAdd, and PersonRemove components in App.js for both class-based and hooks-based implementations. This approach demonstrates how to manage data listing, creation, and deletion together in a real-world React app, providing a full CRUD experience.

Integrating Class Components: PersonList, PersonAdd, and PersonRemove

In this example, you import all three class-based components and render them in your App.js. This setup allows users to add a person, view the list, and remove a person—all in one place. This pattern is common in dashboards and admin panels.

// src/App.js
import PersonList from './components/PersonList';
import PersonAdd from './components/PersonAdd';
import PersonRemove from './components/PersonRemove';

function App() {
  return (
    <div className="App">
      <h2>Add a Person</h2>
      <PersonAdd />
      <h2>People List</h2>
      <PersonList />
      <h2>Remove a Person</h2>
      <PersonRemove />
    </div>
  );
}

export default App;

Why this integration?
Combining create, read, and delete operations in a single app view allows users to interact with your API in a cohesive way. It also makes it easier to manage state and see the effects of each action in context.

Integrating Functional Components with Hooks: PersonListHooks, PersonAddHooks, and PersonRemoveHooks

For modern React apps, hooks-based components are preferred for their simplicity and flexibility. Here, you import the hooks versions of your API components and render them together in App.js. This pattern is ideal for new projects and teams adopting functional React.

// src/App.js
import PersonListHooks from './components/PersonListHooks';
import PersonAddHooks from './components/PersonAddHooks';
import PersonRemoveHooks from './components/PersonRemoveHooks';

function App() {
  return (
    <div className="App">
      <h2>Add a Person</h2>
      <PersonAddHooks />
      <h2>People List</h2>
      <PersonListHooks />
      <h2>Remove a Person</h2>
      <PersonRemoveHooks />
    </div>
  );
}

export default App;

Why use hooks-based integration?
Hooks components provide better state management, easier side-effect handling, and a more concise syntax. Integrating their hooks versions together in App.js gives you a scalable and maintainable foundation for CRUD operations in your application.

Mixing and Matching: Class and Hooks Components

You can also mix class and hooks components as needed, especially when migrating legacy code or adopting hooks incrementally. For example, you could use PersonAddHooks with PersonList and PersonRemoveHooks:

// src/App.js
import PersonList from './components/PersonList';
import PersonAddHooks from './components/PersonAddHooks';
import PersonRemoveHooks from './components/PersonRemoveHooks';

function App() {
  return (
    <div className="App">
      <PersonAddHooks />
      <PersonList />
      <PersonRemoveHooks />
    </div>
  );
}

export default App;

Takeaway:
App-level integration of your API components—whether class-based or hooks-based—enables a seamless, practical user experience. This approach mirrors real-world production apps, where you often need to combine multiple data operations in a single UI.

Centralize data fetching with a reusable hook that wraps a shared Axios instance. This pattern provides loading/error state, status codes, cancellation, and an imperative execute() for on‑demand calls.

// src/hooks/useAxios.js
import { useCallback, useEffect, useRef, useState } from 'react';
import API from '../api'; // the configured axios instance

/**
 * useAxios — generic Axios hook
 * @param {Object} options - { url, method, data, params, headers, immediate }
 * @returns {Object} { data, error, loading, status, execute, cancel }
 */
export default function useAxios({ url, method = 'get', data = undefined, params = undefined, headers = undefined, immediate = true } = {}) {
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  const [status, setStatus] = useState(undefined);
  const controllerRef = useRef(null);

  const execute = useCallback(async (override = {}) => {
    setLoading(true);
    setError(null);
    controllerRef.current?.abort();
    controllerRef.current = new AbortController();
    try {
      const res = await API.request({
        url,
        method,
        data,
        params,
        headers,
        signal: controllerRef.current.signal,
        ...override,
      });
      setStatus(res.status);
      setResponse(res.data);
      return res;
    } catch (err) {
      // do not treat cancellation as an error
      if (err.name === 'CanceledError') return;
      setStatus(err.response?.status);
      setError(err);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [url, method, data, params, headers]);

  const cancel = useCallback(() => controllerRef.current?.abort(), []);

  useEffect(() => {
    if (immediate && url) execute();
    return () => controllerRef.current?.abort();
  }, [immediate, url, execute]);

  return { data: response, error, loading, status, execute, cancel };
}

Example: List Users with useAxios

// src/components/UsersWithHook.js
import React from 'react';
import useAxios from '../hooks/useAxios';

export default function UsersWithHook() {
  const { data: users, loading, error, status, execute } = useAxios({ url: 'users' });

  if (loading) return <p>Loading…</p>;
  if (error) return <p role="alert">Failed ({status ?? 'N/A'}). Try again.</p>;

  return (
    <div>
      <button onClick={() => execute()}>Reload</button>
      <ul>
        {(users || []).map(u => (<li key={u.id}>{u.name}</li>))}
      </ul>
    </div>
  );
}

Why this helps: Consistent UX, fewer foot‑guns, and one place to evolve timeouts, headers, and interceptors as the app grows.

Advanced Implementations with Axios in React

For more complex requirements in React projects, Axios offers powerful features such as pagination, concurrent requests, file uploads with progress, and advanced timeout control. Here are practical patterns for each:

Pagination & Params Example

Use query params and inspect response headers for pagination:

import API from './api';

async function fetchUsersPage(page = 1, limit = 5) {
  const res = await API.get('users', { params: { _page: page, _limit: limit } });
  // JSONPlaceholder sends total count in headers
  const total = res.headers['x-total-count'];
  console.log('Total users:', total);
  return res.data;
}

Concurrent Requests Example

Fetch users and posts at the same time:

import API from './api';

async function fetchUsersAndPosts() {
  const [usersRes, postsRes] = await Promise.all([
    API.get('users'),
    API.get('posts'),
  ]);
  return { users: usersRes.data, posts: postsRes.data };
}

File Upload with Progress Example

Track upload progress using Axios’s onUploadProgress:

import React, { useState } from 'react';
import API from './api';

function FileUploader() {
  const [progress, setProgress] = useState(0);
  const [file, setFile] = useState(null);

  const handleChange = (e) => setFile(e.target.files[0]);

  const handleUpload = async () => {
    if (!file) return;
    const form = new FormData();
    form.append('file', file);
    try {
      await API.post('/upload', form, {
        onUploadProgress: (evt) => {
          if (evt.total) setProgress(Math.round((evt.loaded * 100) / evt.total));
        },
      });
      alert('Upload complete!');
    } catch (err) {
      alert('Upload failed!');
    }
  };

  return (
    <div>
      <input type="file" onChange={handleChange} />
      <button onClick={handleUpload}>Upload</button>
      {progress > 0 && <p>Progress: {progress}%</p>}
    </div>
  );
}

Timeout Tuning Example

Set a custom timeout and handle timeouts gracefully:

import axios from 'axios';

const API = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com/',
  timeout: 8000, // 8 seconds
});

async function fetchWithTimeout() {
  try {
    const res = await API.get('users');
    return res.data;
  } catch (err) {
    if (err.code === 'ECONNABORTED') {
      console.error('Request timed out!');
    } else {
      console.error('Other error:', err.message);
    }
  }
}

Tip: Use these advanced patterns to handle real-world requirements like infinite scroll, batch loading, file uploads, and robust network error handling in your React apps.

Using Axios with React MCP Server (Model Context Protocol)

Why this matters (EEAT): React MCP (Model Context Protocol) provides a structured, declarative way to manage data models and server interactions in modern React apps. Pairing it with Axios ensures you retain fine‑grained network control (interceptors, error handling, retries) while leveraging MCP’s context‑driven data flow.

What Is React MCP?

React MCP is an emerging open‑source protocol and server layer that standardizes how React components declare and consume remote data models. Instead of scattering REST or GraphQL calls across components, MCP lets you:

  1. Define models on the server (CRUD endpoints auto‑generated).
  2. Expose context to the React tree, so components subscribe to model slices declaratively.
  3. Auto‑invalidate & refetch when mutations occur — no manual cache wiring.

Repo: https://github.com/kalivaraprasad-gonapa/react-mcp

Why Combine MCP with Axios?

  • Interceptors & Security: Keep your existing Axios instance for auth headers, telemetry, and 401 refresh flows.
  • Uniform Error Handling: MCP surfaces model errors, but Axios still normalizes HTTP/network failures via your shared interceptors.
  • Incremental Adoption: Wrap only the endpoints you migrate to MCP, keep plain Axios elsewhere.

Quick‑Start Integration Example

// src/mcp/index.js  – register MCP context with Axios fetcher
import { createMCPClient } from 'react-mcp/client';
import API from '../api';            // your shared Axios instance
import '../api.interceptors';        // make sure interceptors are loaded once

const mcp = createMCPClient({
  baseURL: 'https://jsonplaceholder.typicode.com/',
  // Use Axios for all underlying HTTP calls so you keep interceptors/timeouts
  fetcher: (cfg) => API.request(cfg),
});

export const MCPProvider = mcp.Provider; // Context provider
export const useModel = mcp.useModel;    // Hook to subscribe to a model
// src/App.js – wrap your app in the MCP provider
import { MCPProvider } from './mcp';
import PersonListHooks from './components/PersonListHooks';

function App() {
  return (
    <MCPProvider>
      <PersonListHooks />
    </MCPProvider>
  );
}
// src/components/PersonListMCP.js – declarative data slice
import { useModel } from '../mcp';

export default function PersonListMCP() {
  // The first arg is the server‑side *model name*; second is query/filter params
  const { data: users, loading, error, refetch } = useModel('users', { limit: 10 });

  if (loading) return <p>Loading users…</p>;
  if (error)   return <p role="alert">{error.message}</p>;

  return (
    <div>
      <button onClick={refetch}>Reload</button>
      <ul>
        {users.map((u) => <li key={u.id}>{u.name}</li>)}
      </ul>
    </div>
  );
}

Best Practices for Axios + MCP

  • Keep a single Axios instance: Pass it to MCP’s fetcher so interceptors (auth, retry, telemetry) apply everywhere.
  • Normalize MCP errors: Wrap MCP errors in your Axios error normalizer so UI components get consistent messages.
  • Leverage MCP invalidation: When you mutate (createUser), MCP automatically invalidates users queries — no manual cache busting.
  • Security: Never expose secrets in model definitions; use interceptors to inject tokens securely.
  • Observability: Log MCP model names + Axios X‑Request‑ID for end‑to‑end tracing in distributed systems.

Result: You keep Axios’ low‑level power (timeouts, cancellation) while gaining MCP’s declarative data layer — a future‑proof architecture for large React apps.

FAQs

Q1: How do I install Axios in a React project?
Install with your package manager and import it where needed. Prefer a shared instance for consistency.

npm install axios   # or: yarn add axios
import axios from 'axios';
// or centralize config
// import API from './api';

See the official Axios GitHub docs for options like timeouts and headers.

Q2: How do I make a GET request with Axios in React?
Call axios.get(url) (or API.get(path) from a configured instance) inside useEffect and update state with the response. Always handle errors and consider cancellation for unmounts.

useEffect(() => {
  const controller = new AbortController();
  axios.get('/users', { signal: controller.signal })
    .then(r => setData(r.data))
    .catch(console.error);
  return () => controller.abort();
}, []);

Q3: How do I make a POST request with Axios in React?
Use axios.post(url, payload) with async/await. Log res.status for visibility and include headers for JSON or auth when needed.

try {
  const res = await axios.post('/users', { name });
  console.log(res.status, res.data);
} catch (err) {
  // handle err.response / err.request / err.message
}

A configured instance helps apply common headers automatically.

Q4: How do I handle errors with Axios in React?
Wrap calls in try/catch and branch on err.response (HTTP status), err.request (no response), and err.message (setup/timeouts). Show friendly UI text, log diagnostics privately, and cancel inflight requests on unmount. See the How to Handle Errors section for a production pattern and status-to-message mapping.

Q5: What are Axios interceptors and how do I use them in React?
Interceptors run before requests and after responses. Use a shared instance to attach auth tokens, correlation IDs, and normalize errors/telemetry. Example: add Authorization: Bearer <token> in a request interceptor and retry once on 401 in a response interceptor. Register them once and import the instance across components.

Q6: Should I use Axios or Fetch in React?
Both work. Axios reduces boilerplate, auto-parses JSON, and supports interceptors/cancellation—great for larger apps. Fetch is built-in and minimal, fine for small utilities. See the Axios vs Fetch comparison table above for syntax, error handling, JSON parsing, and cancellation differences to choose confidently.

Q7: Can I use Axios with async/await in React?
Yes. async/await keeps control flow top‑to‑bottom and makes errors explicit via try/catch. It pairs naturally with hooks like useEffect and useState for readable data fetching.

try {
  const res = await API.delete(`/users/${id}`);
  setResult(res.data);
} catch (err) {
  // status‑aware handling here
}

Q8: How can I cancel Axios requests in React?
Pass an AbortController signal when making the request and call controller.abort() in the effect cleanup or when a user navigates away. This prevents state updates on unmounted components and saves bandwidth—crucial for lists, search-as-you-type, and rapid route changes.

const controller = new AbortController();
axios.get('/users', { signal: controller.signal });
// later
controller.abort();

When building AI-driven applications—such as React dashboards that interact with large language models (LLMs) or other AI APIs—Axios becomes indispensable for reliability and scalability. AI endpoints often require telemetry for observability, automatic retries for transient failures, and robust rate-limit handling to avoid disruptions. By leveraging Axios interceptors and global instances, you can consistently manage authentication tokens, implement retry logic, and centralize error handling across your app. This ensures secure, traceable, and resilient communication with AI providers, enabling your production LLM applications to gracefully handle API quotas, network hiccups, and evolving authentication schemes as you scale.

Conclusion

In this tutorial, you learned how to use Axios with React by walking through practical, real-world scenarios. You covered:

  • Installing Axios in a React project
  • Sending GET requests to fetch data
  • Creating POST requests to add new resources
  • Using DELETE requests with proper error handling and production considerations
  • Centralizing configuration with Axios instances and interceptors
  • Implementing async/await for cleaner, more readable code
  • Building robust error handling patterns, including retries and cancellation
  • Following best practices such as separating API logic, creating reusable hooks, and knowing when to choose Axios over Fetch

By applying these patterns, you’ll write React applications that are more reliable, maintainable, and secure.

If you’d like to deepen your React knowledge, explore the How To Code in React.js series for more examples and projects.

Want to deploy your React project live? Try DigitalOcean App Platform and push your React app from GitHub to production in minutes.

Further Learning

Expand your knowledge and stay up to date with these resources:

Tip: For even deeper dives, consider exploring topics like React Query, TanStack Query, or advanced error handling patterns with Axios. The React and Axios communities are active and regularly publish new guides and best practices.

References

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

PaulHalliday
PaulHalliday
Author
See author profile

I create educational content over at YouTube and https://developer.school.

Vinayak Baranwal
Vinayak Baranwal
Editor
See author profile

Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.

Category:
Tags:

Still looking for an answer?

Was this helpful?


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

A good shot,Thanks really enjoyed it

nice tutorial on how to use axios with React

Great article! I saw it’s using class component. Are there any updates to use the newer react version that uses functional component?

For the last part, isn’t “await” need to use inside async function? Sorry I’m a javascript newbie…

For the last part, isn’t “await” need to use inside async function? Sorry I’m a javascript newbie…

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.