Learn to integrate APIs and handle data fetching in React Native applications.
Learn to integrate APIs and handle data fetching in React Native applications.
Learn to use Fetch API and Axios for making HTTP requests in React Native applications, understanding their differences and use cases
Content by: Pratik Keshvala
React Native Developer
Fetch API is a modern, promise-based API for making HTTP requests. It's built into React Native and provides a clean, simple interface for network requests.
Axios is a popular HTTP client library that provides more features than Fetch, including automatic JSON parsing, request/response interceptors, and better error handling.
Install Axios: `npm install axios`. Axios works out of the box with React Native and provides better TypeScript support and more features than Fetch.
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, FlatList, ActivityIndicator, Alert } from 'react-native';
import axios from 'axios';
// API Configuration
const API_BASE_URL = 'https://jsonplaceholder.typicode.com';
// API Service
const apiService = {
// Fetch API example
fetchUsers: async () => {
try {
const response = await fetch(`${API_BASE_URL}/users`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
},
// Axios example
fetchPosts: async () => {
try {
const response = await axios.get(`${API_BASE_URL}/posts`);
return response.data;
} catch (error) {
console.error('Axios error:', error);
throw error;
}
},
// POST request with Axios
createPost: async (postData) => {
try {
const response = await axios.post(`${API_BASE_URL}/posts`, postData, {
headers: {
'Content-Type': 'application/json',
},
});
return response.data;
} catch (error) {
console.error('Create post error:', error);
throw error;
}
},
// PUT request with Axios
updatePost: async (id, postData) => {
try {
const response = await axios.put(`${API_BASE_URL}/posts/${id}`, postData);
return response.data;
} catch (error) {
console.error('Update post error:', error);
throw error;
}
},
// DELETE request with Axios
deletePost: async (id) => {
try {
const response = await axios.delete(`${API_BASE_URL}/posts/${id}`);
return response.data;
} catch (error) {
console.error('Delete post error:', error);
throw error;
}
},
};
// User Component
const UserList = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadUsers = async () => {
setLoading(true);
setError(null);
try {
const userData = await apiService.fetchUsers();
setUsers(userData);
} catch (err) {
setError(err.message);
Alert.alert('Error', 'Failed to load users');
} finally {
setLoading(false);
}
};
useEffect(() => {
loadUsers();
}, []);
const renderUser = ({ item }) => (
<View style={styles.userItem}>
<Text style={styles.userName}>{item.name}</Text>
<Text style={styles.userEmail}>{item.email}</Text>
<Text style={styles.userWebsite}>{item.website}</Text>
</View>
);
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Users (Fetch API)</Text>
{loading && <ActivityIndicator size="large" color="#2196F3" />}
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
<TouchableOpacity style={styles.retryButton} onPress={loadUsers}>
<Text style={styles.retryButtonText}>Retry</Text>
</TouchableOpacity>
</View>
)}
<FlatList
data={users}
keyExtractor={item => item.id.toString()}
renderItem={renderUser}
style={styles.list}
refreshing={loading}
onRefresh={loadUsers}
/>
</View>
);
};
// Posts Component
const PostList = () => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadPosts = async () => {
setLoading(true);
setError(null);
try {
const postData = await apiService.fetchPosts();
setPosts(postData.slice(0, 10)); // Limit to first 10 posts
} catch (err) {
setError(err.message);
Alert.alert('Error', 'Failed to load posts');
} finally {
setLoading(false);
}
};
const createNewPost = async () => {
const newPost = {
title: 'New Post',
body: 'This is a new post created from the app',
userId: 1,
};
try {
const createdPost = await apiService.createPost(newPost);
setPosts(prev => [createdPost, ...prev]);
Alert.alert('Success', 'Post created successfully!');
} catch (err) {
Alert.alert('Error', 'Failed to create post');
}
};
const deletePost = async (id) => {
try {
await apiService.deletePost(id);
setPosts(prev => prev.filter(post => post.id !== id));
Alert.alert('Success', 'Post deleted successfully!');
} catch (err) {
Alert.alert('Error', 'Failed to delete post');
}
};
useEffect(() => {
loadPosts();
}, []);
const renderPost = ({ item }) => (
<View style={styles.postItem}>
<Text style={styles.postTitle}>{item.title}</Text>
<Text style={styles.postBody}>{item.body}</Text>
<View style={styles.postActions}>
<TouchableOpacity
style={styles.actionButton}
onPress={() => deletePost(item.id)}
>
<Text style={styles.actionButtonText}>Delete</Text>
</TouchableOpacity>
</View>
</View>
);
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Posts (Axios)</Text>
<TouchableOpacity style={styles.createButton} onPress={createNewPost}>
<Text style={styles.createButtonText}>Create New Post</Text>
</TouchableOpacity>
{loading && <ActivityIndicator size="large" color="#2196F3" />}
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
<TouchableOpacity style={styles.retryButton} onPress={loadPosts}>
<Text style={styles.retryButtonText}>Retry</Text>
</TouchableOpacity>
</View>
)}
<FlatList
data={posts}
keyExtractor={item => item.id.toString()}
renderItem={renderPost}
style={styles.list}
refreshing={loading}
onRefresh={loadPosts}
/>
</View>
);
};
// Main App
const App = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>API Integration Example</Text>
<UserList />
<PostList />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
section: {
marginBottom: 30,
backgroundColor: 'white',
padding: 20,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 15,
color: '#333',
},
list: {
maxHeight: 200,
},
userItem: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
userName: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
marginBottom: 5,
},
userEmail: {
fontSize: 14,
color: '#666',
marginBottom: 2,
},
userWebsite: {
fontSize: 12,
color: '#999',
},
postItem: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
postTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
marginBottom: 5,
},
postBody: {
fontSize: 14,
color: '#666',
marginBottom: 10,
},
postActions: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
actionButton: {
backgroundColor: '#f44336',
padding: 8,
borderRadius: 5,
},
actionButtonText: {
color: 'white',
fontSize: 12,
fontWeight: 'bold',
},
createButton: {
backgroundColor: '#4CAF50',
padding: 15,
borderRadius: 8,
alignItems: 'center',
marginBottom: 15,
},
createButtonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
errorContainer: {
alignItems: 'center',
padding: 20,
},
errorText: {
color: '#f44336',
fontSize: 14,
textAlign: 'center',
marginBottom: 10,
},
retryButton: {
backgroundColor: '#2196F3',
padding: 10,
borderRadius: 5,
},
retryButtonText: {
color: 'white',
fontSize: 14,
fontWeight: 'bold',
},
});
export default App;Test your understanding of this topic:
Master async/await patterns for handling asynchronous operations in React Native applications, including error handling and concurrent requests
Content by: Pratik Keshvala
React Native Developer
Async/await is a modern way to handle asynchronous operations in JavaScript. It makes asynchronous code look and behave more like synchronous code, making it easier to read and debug.
Always provide loading states for async operations to improve user experience. Use loading indicators, skeleton screens, or progress bars to show that data is being fetched.
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator, Alert } from 'react-native';
// Mock API functions
const mockApi = {
fetchUser: async (id) => {
await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate network delay
return { id, name: `User ${id}`, email: `user${id}@example.com` };
},
fetchPosts: async (userId) => {
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, title: `Post 1 by User ${userId}`, body: 'Post content...' },
{ id: 2, title: `Post 2 by User ${userId}`, body: 'Post content...' },
];
},
fetchComments: async (postId) => {
await new Promise(resolve => setTimeout(resolve, 600));
return [
{ id: 1, text: `Comment 1 on Post ${postId}` },
{ id: 2, text: `Comment 2 on Post ${postId}` },
];
},
};
// Sequential Loading Example
const SequentialLoading = () => {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadDataSequentially = async () => {
setLoading(true);
setError(null);
try {
// Load user first
const userData = await mockApi.fetchUser(1);
setUser(userData);
// Then load posts
const postsData = await mockApi.fetchPosts(userData.id);
setPosts(postsData);
// Finally load comments for first post
if (postsData.length > 0) {
const commentsData = await mockApi.fetchComments(postsData[0].id);
setComments(commentsData);
}
} catch (err) {
setError(err.message);
Alert.alert('Error', 'Failed to load data sequentially');
} finally {
setLoading(false);
}
};
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Sequential Loading</Text>
<TouchableOpacity style={styles.button} onPress={loadDataSequentially}>
<Text style={styles.buttonText}>Load Data Sequentially</Text>
</TouchableOpacity>
{loading && <ActivityIndicator size="large" color="#2196F3" />}
{error && <Text style={styles.errorText}>{error}</Text>}
{user && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>User: {user.name}</Text>
<Text style={styles.dataSubtitle}>Email: {user.email}</Text>
</View>
)}
{posts.length > 0 && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>Posts ({posts.length})</Text>
{posts.map(post => (
<Text key={post.id} style={styles.dataItem}>⢠{post.title}</Text>
))}
</View>
)}
{comments.length > 0 && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>Comments ({comments.length})</Text>
{comments.map(comment => (
<Text key={comment.id} style={styles.dataItem}>⢠{comment.text}</Text>
))}
</View>
)}
</View>
);
};
// Concurrent Loading Example
const ConcurrentLoading = () => {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadDataConcurrently = async () => {
setLoading(true);
setError(null);
try {
// Load user and posts concurrently
const [userData, postsData] = await Promise.all([
mockApi.fetchUser(2),
mockApi.fetchPosts(2)
]);
setUser(userData);
setPosts(postsData);
} catch (err) {
setError(err.message);
Alert.alert('Error', 'Failed to load data concurrently');
} finally {
setLoading(false);
}
};
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Concurrent Loading</Text>
<TouchableOpacity style={styles.button} onPress={loadDataConcurrently}>
<Text style={styles.buttonText}>Load Data Concurrently</Text>
</TouchableOpacity>
{loading && <ActivityIndicator size="large" color="#2196F3" />}
{error && <Text style={styles.errorText}>{error}</Text>}
{user && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>User: {user.name}</Text>
<Text style={styles.dataSubtitle}>Email: {user.email}</Text>
</View>
)}
{posts.length > 0 && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>Posts ({posts.length})</Text>
{posts.map(post => (
<Text key={post.id} style={styles.dataItem}>⢠{post.title}</Text>
))}
</View>
)}
</View>
);
};
// Error Handling Example
const ErrorHandling = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadDataWithErrorHandling = async () => {
setLoading(true);
setError(null);
try {
// Simulate an API call that might fail
const shouldFail = Math.random() > 0.5;
if (shouldFail) {
throw new Error('Simulated API error');
}
await new Promise(resolve => setTimeout(resolve, 1000));
setData({ message: 'Data loaded successfully!' });
} catch (err) {
setError(err.message);
console.error('API Error:', err);
} finally {
setLoading(false);
}
};
const retry = () => {
loadDataWithErrorHandling();
};
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Error Handling</Text>
<TouchableOpacity style={styles.button} onPress={loadDataWithErrorHandling}>
<Text style={styles.buttonText}>Load Data (May Fail)</Text>
</TouchableOpacity>
{loading && <ActivityIndicator size="large" color="#2196F3" />}
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Error: {error}</Text>
<TouchableOpacity style={styles.retryButton} onPress={retry}>
<Text style={styles.retryButtonText}>Retry</Text>
</TouchableOpacity>
</View>
)}
{data && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>{data.message}</Text>
</View>
)}
</View>
);
};
// Main App
const App = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>Async/Await Patterns</Text>
<SequentialLoading />
<ConcurrentLoading />
<ErrorHandling />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
section: {
marginBottom: 30,
backgroundColor: 'white',
padding: 20,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 15,
color: '#333',
},
button: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 8,
alignItems: 'center',
marginBottom: 15,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
dataContainer: {
backgroundColor: '#f0f0f0',
padding: 15,
borderRadius: 8,
marginBottom: 10,
},
dataTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
marginBottom: 5,
},
dataSubtitle: {
fontSize: 14,
color: '#666',
marginBottom: 5,
},
dataItem: {
fontSize: 14,
color: '#666',
marginBottom: 2,
},
errorText: {
color: '#f44336',
fontSize: 14,
textAlign: 'center',
marginBottom: 10,
},
errorContainer: {
alignItems: 'center',
padding: 20,
},
retryButton: {
backgroundColor: '#2196F3',
padding: 10,
borderRadius: 5,
},
retryButtonText: {
color: 'white',
fontSize: 14,
fontWeight: 'bold',
},
});
export default App;Test your understanding of this topic:
Learn comprehensive error handling strategies for API calls and network requests in React Native applications
Content by: Pratik Keshvala
React Native Developer
Implement robust error handling for network requests, including retry mechanisms, timeout handling, and user-friendly error messages.
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, NetInfo } from '@react-native-community/netinfo';
// Error handling utility
class ApiError extends Error {
constructor(message, status, code) {
super(message);
this.name = 'ApiError';
this.status = status;
this.code = code;
}
}
// API service with error handling
const apiService = {
baseURL: 'https://jsonplaceholder.typicode.com',
timeout: 10000,
async request(url, options = {}) {
try {
// Check network connectivity
const netInfo = await NetInfo.fetch();
if (!netInfo.isConnected) {
throw new ApiError('No internet connection', 0, 'NETWORK_ERROR');
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(`${this.baseURL}${url}`, {
...options,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new ApiError(
`HTTP ${response.status}: ${response.statusText}`,
response.status,
'HTTP_ERROR'
);
}
const data = await response.json();
return data;
} catch (error) {
if (error.name === 'AbortError') {
throw new ApiError('Request timeout', 408, 'TIMEOUT_ERROR');
}
throw error;
}
},
async get(url) {
return this.request(url, { method: 'GET' });
},
async post(url, data) {
return this.request(url, {
method: 'POST',
body: JSON.stringify(data),
});
},
};
// Error boundary component
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<View style={styles.errorBoundary}>
<Text style={styles.errorBoundaryTitle}>Something went wrong</Text>
<Text style={styles.errorBoundaryText}>
{this.state.error?.message || 'Unknown error occurred'}
</Text>
<TouchableOpacity
style={styles.retryButton}
onPress={() => this.setState({ hasError: false, error: null })}
>
<Text style={styles.retryButtonText}>Try Again</Text>
</TouchableOpacity>
</View>
);
}
return this.props.children;
}
}
// Error handling hook
const useErrorHandler = () => {
const [error, setError] = useState(null);
const [retryCount, setRetryCount] = useState(0);
const handleError = (error) => {
console.error('Error caught:', error);
setError(error);
};
const retry = () => {
setError(null);
setRetryCount(prev => prev + 1);
};
const reset = () => {
setError(null);
setRetryCount(0);
};
return { error, retryCount, handleError, retry, reset };
};
// Data fetching hook with error handling
const useApiData = (url, options = {}) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const { error, retryCount, handleError, retry, reset } = useErrorHandler();
const fetchData = async () => {
setLoading(true);
try {
const result = await apiService.get(url);
setData(result);
reset();
} catch (err) {
handleError(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url, retryCount]);
return { data, loading, error, retry };
};
// Error display component
const ErrorDisplay = ({ error, onRetry }) => {
const getErrorMessage = (error) => {
if (error instanceof ApiError) {
switch (error.code) {
case 'NETWORK_ERROR':
return 'Please check your internet connection and try again.';
case 'TIMEOUT_ERROR':
return 'Request timed out. Please try again.';
case 'HTTP_ERROR':
return `Server error (${error.status}). Please try again later.`;
default:
return error.message;
}
}
return 'An unexpected error occurred. Please try again.';
};
const getErrorIcon = (error) => {
if (error instanceof ApiError) {
switch (error.code) {
case 'NETWORK_ERROR':
return 'š”';
case 'TIMEOUT_ERROR':
return 'ā°';
case 'HTTP_ERROR':
return 'ā ļø';
default:
return 'ā';
}
}
return 'ā';
};
return (
<View style={styles.errorContainer}>
<Text style={styles.errorIcon}>{getErrorIcon(error)}</Text>
<Text style={styles.errorTitle}>Oops! Something went wrong</Text>
<Text style={styles.errorMessage}>{getErrorMessage(error)}</Text>
<TouchableOpacity style={styles.retryButton} onPress={onRetry}>
<Text style={styles.retryButtonText}>Try Again</Text>
</TouchableOpacity>
</View>
);
};
// Main component
const ApiErrorHandling = () => {
const { data, loading, error, retry } = useApiData('/posts');
return (
<ErrorBoundary>
<View style={styles.container}>
<Text style={styles.title}>Error Handling Example</Text>
{loading && (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
{error && <ErrorDisplay error={error} onRetry={retry} />}
{data && !error && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>Posts Loaded Successfully!</Text>
<Text style={styles.dataCount}>Total posts: {data.length}</Text>
{data.slice(0, 3).map(post => (
<Text key={post.id} style={styles.dataItem}>
⢠{post.title}
</Text>
))}
</View>
)}
</View>
</ErrorBoundary>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
loadingContainer: {
alignItems: 'center',
padding: 20,
},
loadingText: {
fontSize: 16,
color: '#666',
},
errorBoundary: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
errorBoundaryTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#f44336',
marginBottom: 10,
},
errorBoundaryText: {
fontSize: 16,
color: '#666',
textAlign: 'center',
marginBottom: 20,
},
errorContainer: {
alignItems: 'center',
padding: 20,
backgroundColor: '#ffebee',
borderRadius: 8,
marginBottom: 20,
},
errorIcon: {
fontSize: 48,
marginBottom: 10,
},
errorTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#f44336',
marginBottom: 10,
},
errorMessage: {
fontSize: 14,
color: '#666',
textAlign: 'center',
marginBottom: 20,
},
retryButton: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 8,
},
retryButtonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
dataContainer: {
backgroundColor: '#e8f5e8',
padding: 20,
borderRadius: 8,
},
dataTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#4CAF50',
marginBottom: 10,
},
dataCount: {
fontSize: 14,
color: '#666',
marginBottom: 10,
},
dataItem: {
fontSize: 14,
color: '#333',
marginBottom: 5,
},
});
export default ApiErrorHandling;Test your understanding of this topic:
Implement effective loading states and user feedback for async operations in React Native applications
Content by: Pratik Keshvala
React Native Developer
Provide visual feedback during async operations to improve user experience. Use loading indicators, skeleton screens, and progress bars to show that data is being fetched.
import React, { useState, useEffect } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ActivityIndicator,
FlatList,
RefreshControl
} from 'react-native';
// Mock API with delay
const mockApi = {
fetchUsers: async () => {
await new Promise(resolve => setTimeout(resolve, 2000));
return [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com' },
];
},
};
// Skeleton component
const SkeletonItem = () => (
<View style={styles.skeletonItem}>
<View style={styles.skeletonAvatar} />
<View style={styles.skeletonContent}>
<View style={styles.skeletonLine} />
<View style={[styles.skeletonLine, styles.skeletonLineShort]} />
</View>
</View>
);
// Loading component
const LoadingComponent = ({ message = 'Loading...' }) => (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#2196F3" />
<Text style={styles.loadingText}>{message}</Text>
</View>
);
// Main component
const LoadingStates = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState(null);
const loadUsers = async (isRefresh = false) => {
if (isRefresh) {
setRefreshing(true);
} else {
setLoading(true);
}
setError(null);
try {
const userData = await mockApi.fetchUsers();
setUsers(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
setRefreshing(false);
}
};
const onRefresh = () => {
loadUsers(true);
};
useEffect(() => {
loadUsers();
}, []);
const renderUser = ({ item }) => (
<View style={styles.userItem}>
<View style={styles.avatar}>
<Text style={styles.avatarText}>
{item.name.charAt(0).toUpperCase()}
</Text>
</View>
<View style={styles.userInfo}>
<Text style={styles.userName}>{item.name}</Text>
<Text style={styles.userEmail}>{item.email}</Text>
</View>
</View>
);
if (loading) {
return (
<View style={styles.container}>
<Text style={styles.title}>Loading States Example</Text>
<LoadingComponent message="Fetching users..." />
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>Loading States Example</Text>
<TouchableOpacity style={styles.button} onPress={() => loadUsers()}>
<Text style={styles.buttonText}>Reload Users</Text>
</TouchableOpacity>
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
)}
<FlatList
data={users}
keyExtractor={item => item.id.toString()}
renderItem={renderUser}
style={styles.list}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={['#2196F3']}
tintColor="#2196F3"
/>
}
ListEmptyComponent={
!loading && !error ? (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>No users found</Text>
</View>
) : null
}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
button: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 8,
alignItems: 'center',
marginBottom: 20,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
fontSize: 16,
color: '#666',
marginTop: 10,
},
skeletonItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 15,
backgroundColor: 'white',
marginBottom: 10,
borderRadius: 8,
},
skeletonAvatar: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: '#e0e0e0',
marginRight: 15,
},
skeletonContent: {
flex: 1,
},
skeletonLine: {
height: 16,
backgroundColor: '#e0e0e0',
borderRadius: 4,
marginBottom: 8,
},
skeletonLineShort: {
width: '60%',
},
userItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 15,
backgroundColor: 'white',
marginBottom: 10,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
avatar: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: '#2196F3',
justifyContent: 'center',
alignItems: 'center',
marginRight: 15,
},
avatarText: {
color: 'white',
fontSize: 20,
fontWeight: 'bold',
},
userInfo: {
flex: 1,
},
userName: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
marginBottom: 4,
},
userEmail: {
fontSize: 14,
color: '#666',
},
list: {
flex: 1,
},
errorContainer: {
backgroundColor: '#ffebee',
padding: 15,
borderRadius: 8,
marginBottom: 20,
},
errorText: {
color: '#f44336',
fontSize: 14,
textAlign: 'center',
},
emptyContainer: {
alignItems: 'center',
padding: 40,
},
emptyText: {
fontSize: 16,
color: '#999',
fontStyle: 'italic',
},
});
export default LoadingStates;Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Continue to Module 7