react-native-fetch-client vs. Native Fetch
WHATWG window.fetch is an excellent low-level transport primitive, but it was never designed to handle mobile networking realities like sudden cellular handoffs, token expiry race conditions, and offline caching.
1. Why Raw Fetch Fails in Production Mobile Apps
Browsers run on stable fiber-optic connections; mobile apps run on moving trains, elevators, and spotty cellular networks. Using raw fetch() directly in React Native components introduces serious reliability issues:
2. Code Comparison: Production Readiness
Notice the staggering difference in defensive boilerplate required to safely execute a single authenticated GET request with timeouts, errors, and unwrap logic:
// Native Fetch: 65+ lines of defensive boilerplate required for ONE request
async function fetchUserProfile(userId: string) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 8000); // Manual timeout
try {
const token = await getAuthToken(); // Manual token fetch
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
signal: controller.signal,
});
clearTimeout(timeoutId);
// Native fetch does NOT throw on 4xx/5xx errors!
if (!response.ok) {
let errorBody = {};
try {
errorBody = await response.json();
} catch {}
// If 401: Manual refresh token handling required here,
// but without a mutex, 5 concurrent calls will all refresh simultaneously!
throw new Error(errorBody.message || `HTTP error ${response.status}`);
}
const data = await response.json(); // Second await required
return data;
} catch (error: any) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timed out after 8000ms');
}
// No automatic retry, no offline queue, no request deduplication!
throw error;
}
}// react-native-fetch-client: 6 lines, fully resilient
import { createClient } from 'react-native-fetch-client';
const api = createClient({
baseURL: 'https://api.example.com',
timeout: 8000, // Global timeout
bearerToken: () => getAuthToken(), // Dynamic auth
retry: { maxAttempts: 3, backoff: 'exponential' }, // Auto retry on drop
dedupe: true, // In-flight deduplication
auth: { refresh: () => refreshSession() }, // Atomic 401 single-flight mutex
});
// Clean, safe execution
const [user, error] = await api.safe.get('/users/{userId}', {
pathParams: { userId },
});3. Built On WHATWG Fetch, Not a Replacement
react-native-fetch-client does not replace the native fetch standard — it encapsulates and enhances it. Under the hood, every request dispatches directly through the host platform's native WHATWG Fetch implementation (Hermes C++ bindings on React Native, native window.fetch in browsers, and Undici in Node.js 18+).