🚀 The Smart Way to Call APIs in React Native Using Custom Hooks (2026 Guide)
Stop writing API calls inside your components. Learn how to build a reusable useApi hook with request cancellation using AbortController for clean, scalable, and production-ready React Native applications.
Why Most React Native Projects Become Difficult to Maintain
Every React Native application communicates with APIs.
Whether you’re building:
- Authentication
- E-commerce
- Social Media
- Food Delivery
- Banking
- Healthcare
Eventually you’ll write something like this:
const HomeScreen = () => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchPosts = async () => {
setLoading(true);
const response = await fetch(API_URL);
const result = await response.json();
setPosts(result);
setLoading(false);
};
fetchPosts();
}, []);
return (...);
};Looks simple.
But imagine doing this inside
- Home Screen
- Profile Screen
- Product Screen
- Search Screen
- Wishlist
- Orders
- Notifications
Soon your project becomes difficult to manage.
Why API Logic Shouldn’t Live Inside Components
This is one of the biggest mistakes developers make.
Your component now has two responsibilities:
- Rendering UI
- Fetching data
This violates the Single Responsibility Principle.
Problems include:
❌ Duplicate code
❌ Difficult debugging
❌ Hard to reuse
❌ Poor testing
❌ Memory leaks
❌ Huge screen components
Instead…
Your component should only display UI.
Your hook should handle networking.
The Better Approach
Instead of this
HomeScreen
├── UI
├── Loading
├── Error
├── Fetch API
├── Parsing
├── Retry
├── CancellationWe’ll split responsibilities.
HomeScreen
│
▼
useApi Hook
│
▼
Network RequestNow every screen becomes tiny.
Recommended Folder Structure
src
├── api
│ ├── api.ts
│ ├── endpoints.ts
│
├── hooks
│ ├── useApi.ts
│
├── services
│ ├── userService.ts
│
├── screens
│
└── componentsMuch cleaner.
Building a Reusable useApi Hook
Instead of writing fetch everywhere…
Let’s write it once.
import { useEffect, useState } from 'react';
export const useApi = <T,>(url: string) => {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url, {
signal: controller.signal,
});
if (!response.ok) {
throw new Error('Something went wrong');
}
const result = await response.json();
setData(result);
} catch (err: any) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => controller.abort();
}, [url]);
return {
data,
loading,
error,
};
};Why This Hook Is Better
✅ Reusable
✅ Less Code
✅ Easy Testing
✅ Separation of Concerns
Get AppStitch by Bharat Chhabra’s stories in your inbox
Join Medium for free to get updates from this writer.
✅ Production Ready
Request Cancellation with AbortController
One of the most overlooked features in React Native is AbortController.
Imagine this scenario.
User opens:
Product DetailsAPI takes
5 secondsBefore it finishes…
User goes back.
Without cancelling the request…
The API still finishes.
Then React tries to update a component that no longer exists.
This can lead to warnings and unnecessary work.
The Solution
const controller = new AbortController();
fetch(API_URL, {
signal: controller.signal,
});Cleanup
return () => {
controller.abort();
};That’s it.
When the component unmounts…
The request is cancelled automatically.
Why AbortController Matters
Imagine a user rapidly switching between screens:
Home
↓
Products
↓
Wishlist
↓
CartWithout cancellation:
4 requests running
↓
Only Cart is visible
↓
Home request finishes
↓
Products request finishes
↓
Wishlist request finishesAll those network calls were unnecessary.
With AbortController:
Home ❌ Cancelled
Products ❌ Cancelled
Wishlist ❌ Cancelled
Cart ✅ ContinuesOnly the active request completes.
Using the Hook
Now your screen becomes incredibly clean.
const HomeScreen = () => {
const {
data,
loading,
error,
} = useApi<Post[]>(API_URL);
...
}No fetch.
No useEffect.
No loading state.
No cancellation logic.
Just UI.
Benefits of This Architecture
✔ Smaller components
✔ Easier maintenance
✔ Better code reuse
✔ Cleaner folder structure
✔ Easier testing
✔ Better scalability
✔ Automatic request cancellation
Final Thoughts
As your React Native application grows, organizing API logic becomes just as important as designing the UI. By moving networking code into a reusable custom hook and using AbortController to cancel unnecessary requests, your components stay focused on presentation while your data layer remains reusable and easy to maintain.
This approach scales far better than placing fetch() calls inside every screen and provides a solid foundation for larger production applications.
🙌 Thank You for Reading!
Thank you for reading until the end. Before you go:
Please consider clapping 👏 and following if you found this article helpful.
If this guide saved you time or helped you improve your React Native architecture, your support motivates me to keep creating practical, production-focused tutorials.
🚀 Connect with AppStitch
🎥 YouTube: https://www.youtube.com/@AppStitch
📝 Medium: https://medium.com/@appstitch.dev
📸 Instagram: https://www.instagram.com/app_stitch
Happy Coding! 💙










