Chapter 14: API Integration, Async Operations & Error Handling
In previous chapters, we stored data locally using Context API and AsyncStorage.
However, real-world applications usually communicate with backend servers.
Examples:
- Uber fetches ride information from APIs
- Instagram loads posts from backend servers
- Banking apps fetch account balances from secure APIs
Similarly, ExpenseApp should also be able to:
- Fetch expenses from server
- Add new expenses
- Update existing expenses
- Delete expenses
- Handle loading and network failures
In this chapter, we learn how to integrate APIs into our React Native application using:
✅ Fetch API ✅ Axios ✅ Async programming ✅ Loading states ✅ Error handling ✅ Service layer architecture
🎯 What You Will Learn
By the end of this chapter, you will:
- Understand REST APIs
- Learn client-server architecture
- Perform CRUD operations
- Use Fetch and Axios
- Work with mock APIs
- Handle loading and error states
- Use async/await
- Implement try/catch/finally
- Create reusable API service layers
- Structure scalable networking code
🌐 Why APIs are Important
Modern mobile applications rarely store all data inside the app itself.
Instead, applications communicate with backend servers.
APIs help applications:
- Fetch data
- Save records
- Update information
- Authenticate users
- Sync data across devices
🔹 Client-Server Architecture
React Native App
↓
REST API Server
↓
Database
👉 The mobile app communicates with backend APIs using HTTP requests.
🔹 What is a REST API?
REST (Representational State Transfer) is a standard way for applications to communicate over the internet.
REST APIs use HTTP methods to perform operations on data.
🔹 Common HTTP Methods
| Method | Purpose | ExpenseApp Example |
|---|---|---|
| GET | Fetch data | Get all expenses |
| POST | Create data | Add expense |
| PUT | Update data | Update expense |
| DELETE | Remove data | Delete expense |
🔹 Example API Endpoint
https://api.expenseapp.com/expenses
🔹 What is JSON?
JSON (JavaScript Object Notation) is the standard format used for exchanging data between applications and servers.
Example JSON
{
"id": 1,
"description": "Food",
"amount": 250
}
JSON Characteristics
- Lightweight
- Human-readable
- Easy to parse
- Widely used in APIs
🔄 CRUD Operations
CRUD stands for:
| Operation | Meaning |
|---|---|
| Create | Add data |
| Read | Fetch data |
| Update | Modify data |
| Delete | Remove data |
🔹 GET Request (Fetch Data)
const fetchExpenses = async () => {
try {
const response = await fetch("https://api.example.com/expenses");
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
};
👉 Retrieves expense data from server
🔹 POST Request (Create Data)
await fetch("https://api.example.com/expenses", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
description: "Food",
amount: 250,
}),
});
👉 Adds new expense to server
🔹 PUT Request (Update Data)
await fetch("https://api.example.com/expenses/1", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 500,
}),
});
👉 Updates existing expense
🔹 DELETE Request
await fetch("https://api.example.com/expenses/1", {
method: "DELETE",
});
👉 Deletes expense from server
🔹 Understanding async/await
API calls take time because data travels over the internet.
JavaScript handles this using asynchronous programming.
async/await makes asynchronous code easier to understand.
Benefits of async/await
✅ Cleaner code ✅ Easier debugging ✅ Better readability ✅ Simpler error handling
⚖️ Fetch vs Axios
🔹 Fetch API
Fetch is built into JavaScript.
Example
const response = await fetch(url);
const data = await response.json();
Advantages
✅ Built-in ✅ No installation required
Disadvantages
❌ More boilerplate ❌ Manual JSON parsing ❌ Manual error handling
🔹 Axios
Axios is a popular HTTP library.
Install Axios
npx expo install axios
Example
import axios from "axios";
const response = await axios.get("https://api.example.com/expenses");
console.log(response.data);
🔹 Advantages of Axios
✅ Automatic JSON parsing ✅ Better error handling ✅ Request/response interceptors ✅ Cleaner syntax ✅ Timeout support
👉 Axios is preferred in real-world applications.
🧪 Calling Mock APIs
Mock APIs help developers test applications without creating real backend servers.
🔹 Popular Mock APIs
- JSONPlaceholder
- ReqRes
- MockAPI
- Local Express Server
🔹 Example Mock API Request
const fetchPosts = async () => {
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts",
);
console.log(response.data);
} catch (error) {
console.log(error);
}
};
⏳ Handling Loading State
API requests take time.
Applications should inform users while data is loading.
🔹 Loading State Example
const [loading, setLoading] = useState(false);
const [expenses, setExpenses] = useState([]);
const fetchExpenses = async () => {
try {
setLoading(true);
const response = await axios.get("/expenses");
setExpenses(response.data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
🔹 Loading UI
{
loading && <Text>Loading...</Text>;
}
👉 Improves user experience
❌ Handling Error State
Network requests can fail because of:
- No internet
- Server errors
- Invalid API endpoints
- Timeout issues
Applications should handle errors gracefully.
🔹 Error State Example
const [error, setError] = useState<string | null>(null);
🔹 Error Handling
try {
const response = await axios.get("/expenses");
setExpenses(response.data);
} catch (error) {
setError("Unable to load expenses");
}
🔹 Error UI
{
error && (
<Text
style={{
color: "red",
}}
>
{error}
</Text>
);
}
🛡️ try/catch/finally
🔹 Why Important?
try/catch prevents applications from crashing during API failures.
finally always runs whether request succeeds or fails.
🔹 Example
try {
setLoading(true);
const response = await axios.get("/expenses");
setExpenses(response.data);
} catch (error) {
setError("Failed to fetch expenses");
} finally {
setLoading(false);
}
🔹 Common HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized |
| 404 | Not Found |
| 500 | Server Error |
⏱️ Handling Timeouts
Timeouts prevent applications from waiting forever for server responses.
🔹 Axios Timeout
axios.get("/expenses", {
timeout: 5000,
});
👉 Request fails after 5 seconds
🔹 Fetch Timeout using AbortController
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
fetch(url, {
signal: controller.signal,
});
🔹 API Service Layer
Real-world applications separate API logic from UI components.
This improves:
✅ Reusability ✅ Maintainability ✅ Scalability
🔹 Create Axios Instance
📁 src/services/api.ts
import axios from "axios";
const api = axios.create({
baseURL: "https://api.example.com",
timeout: 5000,
});
export default api;
🔹 Create Expense Service
📁 src/services/expenseService.ts
import api from "./api";
export type Expense = {
id: number;
description: string;
amount: number;
};
export const getExpenses = async (): Promise<Expense[]> => {
const response = await api.get("/expenses");
return response.data;
};
export const createExpense = async (expense: Expense) => {
return api.post("/expenses", expense);
};
export const deleteExpense = async (id: number) => {
return api.delete(`/expenses/${id}`);
};
🧪 ExpenseApp Full API Example
📁 src/app/index.tsx
import React, { useEffect, useState } from "react";
import {
ActivityIndicator,
FlatList,
SafeAreaView,
Text,
View,
} from "react-native";
import { Expense, getExpenses } from "../services/expenseService";
export default function HomeScreen() {
const [expenses, setExpenses] = useState<Expense[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchExpenses();
}, []);
const fetchExpenses = async () => {
try {
setLoading(true);
const data = await getExpenses();
setExpenses(data);
} catch (error) {
setError("Failed to load expenses");
} finally {
setLoading(false);
}
};
if (loading) {
return <ActivityIndicator />;
}
if (error) {
return <Text>{error}</Text>;
}
return (
<SafeAreaView>
<FlatList
data={expenses}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View
style={{
padding: 15,
borderBottomWidth: 1,
}}
>
<Text>{item.description}</Text>
<Text>₹{item.amount}</Text>
</View>
)}
/>
</SafeAreaView>
);
}
🔹 Recommended Folder Structure
src
│
├── app
│ └── index.tsx
│
├── services
│ ├── api.ts
│ └── expenseService.ts
│
├── contexts
│ └── ExpenseContext.tsx
│
├── hooks
│ └── useExpenseContext.ts
│
├── components
│ └── ExpenseItem.tsx
│
└── styles
└── styles.ts
✅ Best Practices
✅ Keep API logic separate from UI
✅ Use reusable service files
✅ Always handle loading states
✅ Always handle errors
✅ Use TypeScript types
✅ Use async/await
✅ Use try/catch/finally
✅ Use request timeouts
✅ Keep components focused
⚠️ Common Mistakes
❌ Ignoring loading states
❌ Ignoring API failures
❌ Forgetting async/await
❌ Writing API calls directly inside JSX
❌ Using any type everywhere
❌ Mixing networking logic with UI
❌ Forgetting timeout handling
🧪 Practice Exercises
- Add Update Expense API
- Add Delete Expense API
- Add Retry button on error
- Add Pull-to-Refresh functionality
- Create reusable Loading component
- Add API timeout handling
- Create Auth API service
📘 Chapter Summary
In this chapter, you:
✅ Learned REST API fundamentals
✅ Understood client-server architecture
✅ Performed CRUD operations
✅ Used Fetch and Axios
✅ Worked with mock APIs
✅ Handled loading states
✅ Implemented robust error handling
✅ Used async/await and try/catch
✅ Created reusable API service layers
✅ Built scalable networking architecture