fetch API that’s available in web browsers, making it familiar if you’ve done web development.
The Fetch API
React Native includes the Fetch API for making HTTP requests. From the React Native source (Libraries/Network/fetch.js):
fetch.js (from source)
// side-effectful require() to put fetch,
// Headers, Request, Response in global scope
require('whatwg-fetch');
export const fetch = global.fetch;
export const Headers = global.Headers;
export const Request = global.Request;
export const Response = global.Response;
Basic GET Request
import {useEffect, useState} from 'react';
import {View, Text, ActivityIndicator} from 'react-native';
function UserProfile({userId}) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(`https://api.example.com/users/${userId}`)
.then(response => response.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(error => {
setError(error.message);
setLoading(false);
});
}, [userId]);
if (loading) {
return <ActivityIndicator size="large" />;
}
if (error) {
return <Text>Error: {error}</Text>;
}
return (
<View>
<Text>Name: {user.name}</Text>
<Text>Email: {user.email}</Text>
</View>
);
}
Using Async/Await
Modern JavaScript async/await syntax makes network code cleaner:import {useEffect, useState} from 'react';
import {View, Text, FlatList} from 'react-native';
function PostsList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
loadPosts();
}, []);
const loadPosts = async () => {
try {
const response = await fetch('https://api.example.com/posts');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setPosts(data);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
if (loading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error}</Text>;
return (
<FlatList
data={posts}
renderItem={({item}) => (
<View>
<Text>{item.title}</Text>
</View>
)}
keyExtractor={item => item.id.toString()}
/>
);
}
POST Requests
Sending data to a server:import {useState} from 'react';
import {View, TextInput, Button, Alert} from 'react-native';
function CreatePost() {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [submitting, setSubmitting] = useState(false);
const handleSubmit = async () => {
setSubmitting(true);
try {
const response = await fetch('https://api.example.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: title,
body: body,
}),
});
if (!response.ok) {
throw new Error('Failed to create post');
}
const data = await response.json();
Alert.alert('Success', 'Post created successfully!');
// Reset form
setTitle('');
setBody('');
} catch (error) {
Alert.alert('Error', error.message);
} finally {
setSubmitting(false);
}
};
return (
<View>
<TextInput
placeholder="Title"
value={title}
onChangeText={setTitle}
/>
<TextInput
placeholder="Body"
value={body}
onChangeText={setBody}
multiline
/>
<Button
title="Submit"
onPress={handleSubmit}
disabled={submitting}
/>
</View>
);
}
HTTP Methods
- GET
- POST
- PUT
- DELETE
// Retrieve data
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// Create new resource
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: 'John'}),
});
// Update existing resource
const response = await fetch('https://api.example.com/data/1', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: 'Jane'}),
});
// Delete resource
const response = await fetch('https://api.example.com/data/1', {
method: 'DELETE',
});
Authentication
Bearer Token Authentication
const TOKEN = 'your-auth-token';
const response = await fetch('https://api.example.com/protected', {
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
});
Complete Auth Example
import {useState, useEffect} from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
function useAuth() {
const [token, setToken] = useState(null);
useEffect(() => {
loadToken();
}, []);
const loadToken = async () => {
const savedToken = await AsyncStorage.getItem('authToken');
setToken(savedToken);
};
const login = async (email, password) => {
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email, password}),
});
const data = await response.json();
if (response.ok) {
await AsyncStorage.setItem('authToken', data.token);
setToken(data.token);
return {success: true};
}
return {success: false, error: data.message};
};
const logout = async () => {
await AsyncStorage.removeItem('authToken');
setToken(null);
};
const fetchWithAuth = async (url, options = {}) => {
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
};
return {token, login, logout, fetchWithAuth};
}
Error Handling
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
// Check HTTP status
if (!response.ok) {
switch (response.status) {
case 400:
throw new Error('Bad request');
case 401:
throw new Error('Unauthorized');
case 404:
throw new Error('Not found');
case 500:
throw new Error('Server error');
default:
throw new Error(`HTTP error! status: ${response.status}`);
}
}
const data = await response.json();
return data;
} catch (error) {
if (error.message === 'Network request failed') {
// No internet connection
Alert.alert('Network Error', 'Please check your internet connection');
} else {
// Other errors
Alert.alert('Error', error.message);
}
throw error;
}
};
Request Timeout
const fetchWithTimeout = async (url, options = {}, timeout = 5000) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
clearTimeout(id);
return response;
} catch (error) {
clearTimeout(id);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
};
Real-World Example: News Feed
import {useState, useEffect} from 'react';
import {
View,
Text,
FlatList,
RefreshControl,
ActivityIndicator,
StyleSheet,
} from 'react-native';
function NewsFeed() {
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
useEffect(() => {
loadArticles();
}, []);
const loadArticles = async (isRefresh = false) => {
if (!hasMore && !isRefresh) return;
try {
const currentPage = isRefresh ? 1 : page;
const response = await fetch(
`https://api.example.com/articles?page=${currentPage}&limit=10`
);
const data = await response.json();
if (isRefresh) {
setArticles(data.articles);
setPage(2);
} else {
setArticles([...articles, ...data.articles]);
setPage(page + 1);
}
setHasMore(data.hasMore);
} catch (error) {
console.error('Error loading articles:', error);
} finally {
setLoading(false);
setRefreshing(false);
}
};
const onRefresh = () => {
setRefreshing(true);
setHasMore(true);
loadArticles(true);
};
const renderArticle = ({item}) => (
<View style={styles.article}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.summary}>{item.summary}</Text>
<Text style={styles.date}>{item.publishedAt}</Text>
</View>
);
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<FlatList
data={articles}
renderItem={renderArticle}
keyExtractor={item => item.id}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
onEndReached={() => loadArticles()}
onEndReachedThreshold={0.5}
ListFooterComponent={
hasMore ? <ActivityIndicator style={styles.footer} /> : null
}
/>
);
}
const styles = StyleSheet.create({
center: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
article: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8,
},
summary: {
fontSize: 14,
color: '#666',
marginBottom: 8,
},
date: {
fontSize: 12,
color: '#999',
},
footer: {
marginVertical: 20,
},
});
export default NewsFeed;
WebSocket Support
For real-time communication:import {useEffect, useState} from 'react';
import {View, Text, FlatList} from 'react-native';
function ChatRoom({roomId}) {
const [messages, setMessages] = useState([]);
const [ws, setWs] = useState(null);
useEffect(() => {
const websocket = new WebSocket('wss://api.example.com/chat');
websocket.onopen = () => {
console.log('Connected to WebSocket');
websocket.send(JSON.stringify({type: 'join', roomId}));
};
websocket.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
websocket.onerror = (error) => {
console.error('WebSocket error:', error);
};
websocket.onclose = () => {
console.log('WebSocket disconnected');
};
setWs(websocket);
return () => {
websocket.close();
};
}, [roomId]);
const sendMessage = (text) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({type: 'message', text}));
}
};
return (
<View>
<FlatList
data={messages}
renderItem={({item}) => <Text>{item.text}</Text>}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
Best Practices
Always handle errors
Always handle errors
Network requests can fail for many reasons. Always wrap fetch calls in try-catch blocks.
Show loading states
Show loading states
Provide feedback to users while data is loading with ActivityIndicator or skeleton screens.
Handle no internet connection
Handle no internet connection
Check for network errors and show appropriate messages to users.
Use async/await
Use async/await
Cleaner than promises for sequential operations and error handling.
Implement request timeouts
Implement request timeouts
Prevent requests from hanging indefinitely.
Cache data when appropriate
Cache data when appropriate
Use AsyncStorage or libraries like React Query to cache responses.
Next Steps
Navigation
Learn to navigate between screens in your app
Using Lists
Display network data in efficient lists