DEV Community

Cover image for React Mastery Series – Day 23: API Integration in React – Fetch, Axios, Error Handling & Best Practices
Siva Samanthapudi
Siva Samanthapudi

Posted on

React Mastery Series – Day 23: API Integration in React – Fetch, Axios, Error Handling & Best Practices

Welcome back to the React Mastery Series!

In the previous article, we explored Redux Toolkit and learned how enterprise applications use centralized state management to build scalable and predictable React applications.

Today, we'll cover another critical skill every React developer must master:

API Integration in React

Most React applications don't work in isolation. They communicate with backend services to:

  • Authenticate users
  • Fetch customer data
  • Display products
  • Process payments
  • Upload files
  • Update user profiles

Understanding how to interact with APIs efficiently is essential for building production-ready applications.


What is an API?

An API (Application Programming Interface) acts as a bridge between the frontend and backend.

 React Application
       |
       ↓
   REST API
       |
       ↓
    Database
Enter fullscreen mode Exit fullscreen mode

Instead of directly accessing the database, the React application sends requests to the backend API.


HTTP Request Methods

The most common HTTP methods are:

Method Purpose
GET Retrieve data
POST Create new data
PUT Update existing data
PATCH Partially update data
DELETE Remove data

Example:

GET    /users
POST   /users
PUT    /users/101
DELETE /users/101
Enter fullscreen mode Exit fullscreen mode

Fetch API

The Fetch API is built into modern browsers.

Example:

async function fetchUsers() {
  const response = await fetch("/api/users");
  const users = await response.json();
  console.log(users);
}
Enter fullscreen mode Exit fullscreen mode

Flow:

  React
     |
  fetch()
     |
  Backend
     |
JSON Response
     |
 React UI
Enter fullscreen mode Exit fullscreen mode

Handling Errors with Fetch

A common mistake is assuming every response is successful.

async function fetchUsers() {

  try {
    const response = await fetch("/api/users");
    if (!response.ok) {
      throw new Error("Failed to fetch users");
    }
    const users = await response.json();
    console.log(users);
  } catch (error) {
    console.error(error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Always check response.ok.


Why Many Teams Prefer Axios

Although Fetch is powerful, enterprise projects often use Axios because it provides:

  • Automatic JSON transformation
  • Request interceptors
  • Response interceptors
  • Request cancellation
  • Timeout configuration
  • Better error handling

Installation:

npm install axios
Enter fullscreen mode Exit fullscreen mode

Making a GET Request with Axios

import axios from "axios";

async function fetchUsers() {
  const response = await axios.get("/api/users");
  console.log(response.data);
}
Enter fullscreen mode Exit fullscreen mode

Notice:

    Fetch
      ↓
response.json()
Enter fullscreen mode Exit fullscreen mode

vs.

    Axios
      ↓
response.data
Enter fullscreen mode Exit fullscreen mode

Axios automatically parses JSON.


Sending a POST Request

Example:

await axios.post("/api/users", {
  name: "Siva",
  role: "Frontend Developer"
});
Enter fullscreen mode Exit fullscreen mode

Flow:

   Form
    |
  Submit
    |
POST Request
    |
 Backend
    |
Database
Enter fullscreen mode Exit fullscreen mode

PUT and DELETE Requests

Update:

await axios.put( "/api/users/101",
  {
    name: "Updated Name"
  }
);
Enter fullscreen mode Exit fullscreen mode

Delete:

await axios.delete("/api/users/101");
Enter fullscreen mode Exit fullscreen mode

Creating a Reusable Axios Instance

Instead of repeating configuration:

axios.get(...)
axios.post(...)
axios.put(...)
Enter fullscreen mode Exit fullscreen mode

Create one API client.

import axios from "axios";
export const api = axios.create({
  baseURL: "https://api.example.com",
  timeout: 10000
});
Enter fullscreen mode Exit fullscreen mode

Now:

api.get("/users");
api.post("/users");
Enter fullscreen mode Exit fullscreen mode

Configuration is centralized.


Request Interceptors

Most enterprise applications require an authentication token.

Instead of adding it manually:

headers: {
 Authorization:"Bearer token"
}
Enter fullscreen mode Exit fullscreen mode

Use an interceptor.

api.interceptors.request.use(config => {
  const token = localStorage.getItem("token");
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});
Enter fullscreen mode Exit fullscreen mode

Every request automatically includes the token.


Response Interceptors

Suppose the backend returns:

401 Unauthorized
Enter fullscreen mode Exit fullscreen mode

Instead of handling it everywhere:

if(error.response.status===401){
// Logout
}
Enter fullscreen mode Exit fullscreen mode

Create a response interceptor.

api.interceptors.response.use(
  response => response,
  error => {
   if(error.response?.status===401){
     // Redirect to Login
   }
  return Promise.reject(error);
});
Enter fullscreen mode Exit fullscreen mode

One place handles authentication failures.


Loading States

Always show feedback while data loads.

const [loading, setLoading] = useState(true);
Enter fullscreen mode Exit fullscreen mode

Example:

if (loading) {
  return <Spinner />;
}
Enter fullscreen mode Exit fullscreen mode

Never leave users wondering if the application is working.


Error States

Display meaningful messages.

if (error) {
  return (<ErrorMessage message={error.message} />);
}
Enter fullscreen mode Exit fullscreen mode

Avoid showing raw server errors to users.


Empty States

An API can return an empty list.

if(users.length===0){
  return <NoUsersFound />;
}
Enter fullscreen mode Exit fullscreen mode

A good UI handles:

  • Loading
  • Success
  • Error
  • Empty data

API Service Layer

Avoid calling APIs directly inside components.

❌ Bad:

function Dashboard(){
 useEffect(()=>{
  axios.get("/users");
 },[]);
}
Enter fullscreen mode Exit fullscreen mode

✅ Better:

services
└── userService.ts
Enter fullscreen mode Exit fullscreen mode
export function getUsers(){
  return api.get("/users");
}
Enter fullscreen mode Exit fullscreen mode

Component:

const users = await getUsers();
Enter fullscreen mode Exit fullscreen mode

Business logic stays separate from UI.


Custom Hooks for API Calls

Combine services with Custom Hooks.

function useUsers(){
  // Fetch users
  // Handle loading
  // Handle errors
}
Enter fullscreen mode Exit fullscreen mode

Component:

const { users, loading,error }=useUsers();
Enter fullscreen mode Exit fullscreen mode

The component focuses only on rendering.


API Folder Structure

A scalable structure:

src
├── api
│   ├── axios.ts
│   ├── interceptors.ts
│
├── services
│   ├── authService.ts
│   ├── userService.ts
│   ├── accountService.ts
│   └── transactionService.ts
Enter fullscreen mode Exit fullscreen mode

Each service manages one business domain.


Real-World Banking Example

Customer opens the dashboard.

Dashboard
   |
useAccounts()
   |
accountService
   |
Axios Client
   |
REST API
   |
Customer Accounts
Enter fullscreen mode Exit fullscreen mode

The UI doesn't know how the request is made.

It simply displays the returned data.


Retry Strategy

Network failures happen.

Example:

  Request
    ↓
Network Error
    ↓
  Retry
    ↓
 Success
Enter fullscreen mode Exit fullscreen mode

Libraries like Axios can be combined with retry mechanisms to improve reliability.


Security Best Practices

Never:

  • Store sensitive information in source code.
  • Expose API keys in frontend applications.
  • Trust client-side validation alone.

Always:

  • Use HTTPS.
  • Store tokens securely.
  • Validate input on both frontend and backend.

Common Mistakes

1. Calling APIs Inside Every Component

Duplicate requests waste bandwidth.

Move API logic into services and reusable hooks.


2. Ignoring Errors

Always handle:

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 500 Internal Server Error

Provide user-friendly feedback.


3. Mixing UI and Business Logic

Keep components focused on rendering.

Move networking code into services.


4. Forgetting Cleanup

When a component unmounts during an API request, avoid updating state afterward.

Use request cancellation or cleanup mechanisms when appropriate.


Best Practices

  • Centralize Axios configuration.
  • Use request and response interceptors.
  • Separate services from UI.
  • Display loading, error, and empty states.
  • Reuse API logic through Custom Hooks.
  • Handle authentication globally.
  • Keep API endpoints configurable through environment variables.

Key Takeaways

Today, we learned:

✅ React applications communicate with backend services using APIs.
✅ Fetch and Axios are the two most common approaches.
✅ Axios provides powerful features like interceptors and automatic JSON parsing.
✅ API logic should live in a service layer, not inside components.
✅ Every application should handle loading, error, and empty states gracefully.
✅ Combining services with Custom Hooks leads to clean and maintainable code.


Coming Next 🚀

In Day 24, we will explore:

React Forms – Controlled Components, Validation & React Hook Form

We will learn:

  • Controlled vs Uncontrolled Components
  • Handling form state
  • Input validation
  • Form submission
  • React Hook Form
  • Schema validation with Zod/Yup
  • Dynamic forms
  • Enterprise form best practices

Forms are one of the most common features in any React application, and mastering them is essential for building production-ready user interfaces.

Happy Coding! 🚀

Top comments (0)