React Native Adapter
The React Native adapter is optimized for mobile applications built with React Native and Expo. It uses the Fetch API (React Native’s built-in polyfill) with mobile-specific features like native Blob and FormData handling, Expo environment detection, and awareness of mobile network conditions.
import rezo from 'rezo/adapters/react-native'; Import and Setup
import rezo, {
Rezo,
RezoError,
RezoHeaders,
RezoFormData
} from 'rezo/adapters/react-native';
// Default instance
const { data } = await rezo.get('https://api.example.com/users');
// Custom instance for your API
const api = new Rezo({
baseURL: 'https://api.myapp.com/v1',
timeout: 15000,
headers: {
'Accept': 'application/json',
'X-App-Version': '2.1.0'
}
}); Environment Detection
The adapter detects the React Native and Expo environments at startup:
// Internal detection (happens automatically)
// Environment.isReactNative = navigator.product === 'ReactNative'
// Environment.isExpo = typeof globalThis.expo !== 'undefined'
// Environment.hasFetch = typeof fetch !== 'undefined'
// Environment.hasBlob = typeof Blob !== 'undefined'
// Environment.hasFormData = typeof FormData !== 'undefined'
// Environment.hasAbortController = typeof AbortController !== 'undefined' This detection is used internally to adjust behavior. For example, the adapter uses React Native’s native FormData implementation when available, which handles file URIs correctly.
Native FormData and File Uploads
React Native’s FormData supports file objects with uri, type, and name properties — the standard way to upload files from the device:
// Image upload from camera roll
const form = new FormData();
form.append('photo', {
uri: 'file:///data/user/0/com.myapp/cache/image.jpg',
type: 'image/jpeg',
name: 'photo.jpg'
});
const { data } = await api.post('/photos/upload', form); With Image Picker
import * as ImagePicker from 'expo-image-picker';
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 0.8
});
if (!result.canceled) {
const form = new FormData();
form.append('avatar', {
uri: result.assets[0].uri,
type: result.assets[0].mimeType || 'image/jpeg',
name: 'avatar.jpg'
});
await api.post('/users/avatar', form);
} With Document Picker
import * as DocumentPicker from 'expo-document-picker';
const result = await DocumentPicker.getDocumentAsync({
type: 'application/pdf'
});
if (result.type === 'success') {
const form = new FormData();
form.append('document', {
uri: result.uri,
type: result.mimeType,
name: result.name
});
await api.post('/documents/upload', form);
} Native Blob Handling
The adapter works with React Native’s Blob implementation for binary data:
// Download binary data
const { data: blob } = await api.get('/files/report.pdf', {
responseType: 'blob'
});
// Use with expo-file-system to save
import * as FileSystem from 'expo-file-system';
const reader = new FileReader();
reader.onload = async () => {
const base64 = reader.result.split(',')[1];
await FileSystem.writeAsStringAsync(
FileSystem.documentDirectory + 'report.pdf',
base64,
{ encoding: FileSystem.EncodingType.Base64 }
);
};
reader.readAsDataURL(blob); Download Progress
The adapter tracks download progress through response body consumption:
const api = new Rezo({
hooks: {
onDownloadProgress: [({ loaded, total, percent }) => {
if (total) {
setProgress(percent / 100);
}
}]
}
});
const { data } = await api.get('/files/large-dataset.json'); Progress Bar Component
import { View, Text } from 'react-native';
import { useState } from 'react';
function DownloadButton({ url }) {
const [progress, setProgress] = useState(0);
const [downloading, setDownloading] = useState(false);
const download = async () => {
setDownloading(true);
const client = new Rezo({
hooks: {
onDownloadProgress: [({ percent }) => setProgress(percent)]
}
});
try {
const { data } = await client.get(url);
// Handle downloaded data
} finally {
setDownloading(false);
setProgress(0);
}
};
return (
<View>
<Text onPress={download}>
{downloading ? `${progress.toFixed(0)}%` : 'Download'}
</Text>
</View>
);
} Timeout and Abort
The adapter supports timeout and manual abort using AbortController:
// Timeout
const { data } = await api.get('/slow-endpoint', {
timeout: 10000
});
// Manual abort (e.g., when component unmounts)
const controller = new AbortController();
useEffect(() => {
const fetchData = async () => {
try {
const { data } = await api.get('/data', {
signal: controller.signal
});
setData(data);
} catch (error) {
if (error.code !== 'ECONNABORTED') {
showError(error.message);
}
}
};
fetchData();
return () => controller.abort();
}, []); Retry Logic
Automatic retries for transient mobile network failures:
const api = new Rezo({
baseURL: 'https://api.myapp.com',
retry: {
limit: 3,
delay: 1000,
backoff: 'exponential',
statusCodes: [408, 429, 500, 502, 503, 504]
}
});
// Retries automatically on transient failures
const { data } = await api.get('/feed'); This is particularly useful for mobile apps where network connectivity is less reliable than desktop environments.
Response Caching
Cache API responses to reduce network usage on mobile:
const api = new Rezo({
baseURL: 'https://api.myapp.com',
cache: {
maxAge: 300000, // 5 minutes
maxEntries: 50
}
});
// First call hits the network
const { data: config } = await api.get('/app/config');
// Second call returns cached data instantly
const { data: cachedConfig } = await api.get('/app/config'); Authentication Pattern
A common pattern for mobile apps with token-based authentication:
const api = new Rezo({
baseURL: 'https://api.myapp.com/v1',
timeout: 15000
});
// Login and store token
async function login(email, password) {
const { data } = await api.post('/auth/login', { email, password });
await SecureStore.setItemAsync('token', data.token);
api.defaults.headers['Authorization'] = `Bearer ${data.token}`;
}
// All subsequent requests include the token
async function getProfile() {
const { data } = await api.get('/users/me');
return data;
}
// Handle token expiration
async function refreshableRequest(method, url, body) {
try {
return await api[method](url, body);
} catch (error) {
if (error.status === 401) {
const token = await refreshToken();
api.defaults.headers['Authorization'] = `Bearer ${token}`;
return await api[method](url, body);
}
throw error;
}
} Error Handling
try {
const { data } = await api.get('/protected-resource');
} catch (error) {
if (RezoError.isRezoError(error)) {
switch (error.status) {
case 401:
// Navigate to login screen
navigation.navigate('Login');
break;
case 403:
Alert.alert('Access Denied', 'You do not have permission.');
break;
case 404:
Alert.alert('Not Found', 'The resource does not exist.');
break;
case 500:
Alert.alert('Server Error', 'Please try again later.');
break;
default:
if (error.code === 'ETIMEDOUT') {
Alert.alert('Timeout', 'The request took too long. Check your connection.');
} else {
Alert.alert('Error', error.message);
}
}
}
} Debug Mode
const { data } = await api.get('/users', {
debug: true
});
// [Rezo Debug] GET https://api.myapp.com/v1/users
// [Rezo Debug] Adapter: react-native
// [Rezo Debug] Response: 200 OK (203.87ms) Limitations
| Feature | Status | Reason |
|---|---|---|
| Proxy support | Not available | React Native does not expose proxy configuration |
| Response streaming | Limited | React Native buffers the entire response |
| HTTP/2 configuration | Not available | Handled by the native networking layer |
| Cookie jar | Not available | Manual header management only |
| TLS configuration | Not available | Managed by iOS/Android native TLS |
| Upload progress | Not available | React Native’s fetch does not expose upload events |
| Direct file I/O | Not available | Use expo-file-system or react-native-fs |
File I/O Workaround
Since React Native does not provide direct filesystem access, use expo-file-system or react-native-fs:
import * as FileSystem from 'expo-file-system';
// Download a file to the device
const downloadResult = await FileSystem.downloadAsync(
'https://example.com/file.pdf',
FileSystem.documentDirectory + 'file.pdf'
);
// For uploads, use file URIs with FormData (see File Uploads section above) Network State Awareness
When @react-native-community/netinfo is installed, you can check network state before making requests:
import NetInfo from '@react-native-community/netinfo';
async function fetchWithNetworkCheck(url) {
const state = await NetInfo.fetch();
if (!state.isConnected) {
throw new RezoError('No network connection', 'ERR_NETWORK');
}
if (state.type === 'cellular' && state.details?.cellularGeneration === '2g') {
// Use longer timeout for slow connections
return api.get(url, { timeout: 30000 });
}
return api.get(url, { timeout: 10000 });
} When to Use This Adapter
The React Native adapter is the right choice when:
- You are building a React Native or Expo application
- You need native FormData handling with file URIs
- You want Blob support for binary data
- You need automatic retry for unreliable mobile networks
- You want consistent Rezo API usage between your mobile and server code
For server-side Node.js APIs that your mobile app talks to, use the HTTP adapter. For web apps (React for web), use the Fetch adapter.