Optimize React Native applications for better performance and user experience.
Optimize React Native applications for better performance and user experience.
Learn to monitor and measure React Native app performance using various tools and techniques
Content by: Pratik Keshvala
React Native Developer
Use Flipper, React DevTools, and native profiling tools to identify performance bottlenecks in your React Native applications.
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { Performance } from 'react-native-performance';
const PerformanceMonitor = () => {
const [metrics, setMetrics] = useState({
fps: 0,
memory: 0,
bundleSize: 0,
startupTime: 0,
});
useEffect(() => {
// Monitor FPS
const fpsInterval = setInterval(() => {
Performance.getFPS().then(fps => {
setMetrics(prev => ({ ...prev, fps }));
});
}, 1000);
// Monitor Memory
const memoryInterval = setInterval(() => {
Performance.getMemoryUsage().then(memory => {
setMetrics(prev => ({ ...prev, memory: memory.usedJSHeapSize }));
});
}, 2000);
// Get Bundle Size
Performance.getBundleSize().then(size => {
setMetrics(prev => ({ ...prev, bundleSize: size }));
});
// Get Startup Time
Performance.getStartupTime().then(time => {
setMetrics(prev => ({ ...prev, startupTime: time }));
});
return () => {
clearInterval(fpsInterval);
clearInterval(memoryInterval);
};
}, []);
const formatBytes = (bytes: number) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const formatTime = (ms: number) => {
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(2)}s`;
};
const getPerformanceStatus = (metric: string, value: number) => {
switch (metric) {
case 'fps':
return value >= 55 ? 'excellent' : value >= 45 ? 'good' : 'poor';
case 'memory':
return value < 50 * 1024 * 1024 ? 'excellent' : value < 100 * 1024 * 1024 ? 'good' : 'poor';
case 'bundleSize':
return value < 1024 * 1024 ? 'excellent' : value < 2 * 1024 * 1024 ? 'good' : 'poor';
case 'startupTime':
return value < 2000 ? 'excellent' : value < 3000 ? 'good' : 'poor';
default:
return 'unknown';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'excellent': return '#4CAF50';
case 'good': return '#FF9800';
case 'poor': return '#F44336';
default: return '#9E9E9E';
}
};
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>Performance Monitor</Text>
<View style={styles.metricsContainer}>
<View style={styles.metricCard}>
<Text style={styles.metricLabel}>Frame Rate</Text>
<Text style={[styles.metricValue, { color: getStatusColor(getPerformanceStatus('fps', metrics.fps)) }]}>
{metrics.fps.toFixed(1)} FPS
</Text>
<Text style={styles.metricStatus}>
Status: {getPerformanceStatus('fps', metrics.fps)}
</Text>
</View>
<View style={styles.metricCard}>
<Text style={styles.metricLabel}>Memory Usage</Text>
<Text style={[styles.metricValue, { color: getStatusColor(getPerformanceStatus('memory', metrics.memory)) }]}>
{formatBytes(metrics.memory)}
</Text>
<Text style={styles.metricStatus}>
Status: {getPerformanceStatus('memory', metrics.memory)}
</Text>
</View>
<View style={styles.metricCard}>
<Text style={styles.metricLabel}>Bundle Size</Text>
<Text style={[styles.metricValue, { color: getStatusColor(getPerformanceStatus('bundleSize', metrics.bundleSize)) }]}>
{formatBytes(metrics.bundleSize)}
</Text>
<Text style={styles.metricStatus}>
Status: {getPerformanceStatus('bundleSize', metrics.bundleSize)}
</Text>
</View>
<View style={styles.metricCard}>
<Text style={styles.metricLabel}>Startup Time</Text>
<Text style={[styles.metricValue, { color: getStatusColor(getPerformanceStatus('startupTime', metrics.startupTime)) }]}>
{formatTime(metrics.startupTime)}
</Text>
<Text style={styles.metricStatus}>
Status: {getPerformanceStatus('startupTime', metrics.startupTime)}
</Text>
</View>
</View>
<View style={styles.recommendationsContainer}>
<Text style={styles.recommendationsTitle}>Performance Recommendations</Text>
<Text style={styles.recommendation}>
• Use FlatList for large lists instead of ScrollView
</Text>
<Text style={styles.recommendation}>
• Implement lazy loading for images and components
</Text>
<Text style={styles.recommendation}>
• Use useMemo and useCallback for expensive calculations
</Text>
<Text style={styles.recommendation}>
• Optimize images with proper formats and sizes
</Text>
<Text style={styles.recommendation}>
• Remove unused dependencies and code
</Text>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
metricsContainer: {
gap: 15,
},
metricCard: {
backgroundColor: 'white',
padding: 20,
borderRadius: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
metricLabel: {
fontSize: 16,
color: '#666',
marginBottom: 5,
},
metricValue: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 5,
},
metricStatus: {
fontSize: 14,
color: '#999',
},
recommendationsContainer: {
marginTop: 30,
backgroundColor: 'white',
padding: 20,
borderRadius: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
recommendationsTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
marginBottom: 15,
},
recommendation: {
fontSize: 14,
color: '#666',
marginBottom: 8,
lineHeight: 20,
},
});
export default PerformanceMonitor;Test your understanding of this topic:
Learn memory management best practices to prevent memory leaks and optimize app performance
Content by: Pratik Keshvala
React Native Developer
Identify and prevent common memory leaks in React Native applications including event listeners, timers, and component references.
Implement strategies like lazy loading, image optimization, and proper cleanup to reduce memory usage.
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { View, Text, StyleSheet, FlatList, Image, TouchableOpacity } from 'react-native';
interface User {
id: number;
name: string;
email: string;
avatar: string;
}
const MemoryOptimizedComponent = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
// Use refs to avoid recreating objects
const timerRef = useRef<NodeJS.Timeout | null>(null);
const subscriptionRef = useRef<any>(null);
const imageCacheRef = useRef<Map<string, string>>(new Map());
// Memoize expensive calculations
const processedUsers = useMemo(() => {
return users.map(user => ({
...user,
displayName: user.name.toUpperCase(),
isSelected: selectedUser?.id === user.id,
}));
}, [users, selectedUser]);
// Memoize callback functions to prevent unnecessary re-renders
const handleUserSelect = useCallback((user: User) => {
setSelectedUser(user);
}, []);
const handleUserDeselect = useCallback(() => {
setSelectedUser(null);
}, []);
// Optimized image loading with caching
const loadImage = useCallback((url: string) => {
if (imageCacheRef.current.has(url)) {
return imageCacheRef.current.get(url);
}
// Simulate image loading and caching
const cachedUrl = url + '?cached=true';
imageCacheRef.current.set(url, cachedUrl);
return cachedUrl;
}, []);
// Cleanup function to prevent memory leaks
const cleanup = useCallback(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
if (subscriptionRef.current) {
subscriptionRef.current.unsubscribe();
subscriptionRef.current = null;
}
// Clear image cache periodically
if (imageCacheRef.current.size > 100) {
imageCacheRef.current.clear();
}
}, []);
useEffect(() => {
const loadUsers = async () => {
setLoading(true);
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
const mockUsers: User[] = Array.from({ length: 50 }, (_, i) => ({
id: i + 1,
name: `User ${i + 1}`,
email: `user${i + 1}@example.com`,
avatar: `https://picsum.photos/100/100?random=${i + 1}`,
}));
setUsers(mockUsers);
} catch (error) {
console.error('Error loading users:', error);
} finally {
setLoading(false);
}
};
loadUsers();
// Set up timer for periodic updates
timerRef.current = setInterval(() => {
console.log('Periodic update - memory usage check');
}, 30000);
// Set up subscription (simulated)
subscriptionRef.current = {
unsubscribe: () => console.log('Subscription cleaned up'),
};
// Cleanup on unmount
return cleanup;
}, [cleanup]);
// Optimized render function for list items
const renderUser = useCallback(({ item }: { item: User & { displayName: string; isSelected: boolean } }) => (
<TouchableOpacity
style={[styles.userItem, item.isSelected && styles.selectedUser]}
onPress={() => handleUserSelect(item)}
>
<Image
source={{ uri: loadImage(item.avatar) }}
style={styles.avatar}
resizeMode="cover"
/>
<View style={styles.userInfo}>
<Text style={styles.userName}>{item.displayName}</Text>
<Text style={styles.userEmail}>{item.email}</Text>
</View>
</TouchableOpacity>
), [handleUserSelect, loadImage]);
// Memoized key extractor
const keyExtractor = useCallback((item: User) => item.id.toString(), []);
return (
<View style={styles.container}>
<Text style={styles.title}>Memory Optimized User List</Text>
{selectedUser && (
<View style={styles.selectedContainer}>
<Text style={styles.selectedTitle}>Selected User:</Text>
<Text style={styles.selectedName}>{selectedUser.name}</Text>
<TouchableOpacity style={styles.deselectButton} onPress={handleUserDeselect}>
<Text style={styles.deselectButtonText}>Deselect</Text>
</TouchableOpacity>
</View>
)}
<FlatList
data={processedUsers}
renderItem={renderUser}
keyExtractor={keyExtractor}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
initialNumToRender={10}
style={styles.list}
/>
<View style={styles.memoryInfo}>
<Text style={styles.memoryText}>
Cached Images: {imageCacheRef.current.size}
</Text>
<Text style={styles.memoryText}>
Total Users: {users.length}
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 20,
color: '#333',
},
selectedContainer: {
backgroundColor: '#e3f2fd',
padding: 15,
borderRadius: 8,
marginBottom: 20,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
selectedTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1976d2',
},
selectedName: {
fontSize: 16,
color: '#333',
marginLeft: 10,
flex: 1,
},
deselectButton: {
backgroundColor: '#f44336',
paddingHorizontal: 15,
paddingVertical: 8,
borderRadius: 5,
},
deselectButtonText: {
color: 'white',
fontWeight: 'bold',
},
list: {
flex: 1,
},
userItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 15,
backgroundColor: 'white',
marginVertical: 5,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2,
},
selectedUser: {
backgroundColor: '#e8f5e8',
borderWidth: 2,
borderColor: '#4caf50',
},
avatar: {
width: 50,
height: 50,
borderRadius: 25,
marginRight: 15,
},
userInfo: {
flex: 1,
},
userName: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
marginBottom: 5,
},
userEmail: {
fontSize: 14,
color: '#666',
},
memoryInfo: {
backgroundColor: 'white',
padding: 15,
borderRadius: 8,
marginTop: 10,
},
memoryText: {
fontSize: 14,
color: '#666',
marginBottom: 5,
},
});
export default MemoryOptimizedComponent;Test your understanding of this topic:
Optimize images for better performance and reduced memory usage in React Native applications
Content by: Pratik Keshvala
React Native Developer
Choose appropriate image formats (WebP, PNG, JPEG) and implement responsive images for different screen densities.
Implement lazy loading for images to improve initial load time and reduce memory usage.
import React, { useState, useCallback, useMemo } from 'react';
import { View, Text, StyleSheet, Image, ActivityIndicator, TouchableOpacity } from 'react-native';
interface OptimizedImageProps {
source: { uri: string };
width?: number;
height?: number;
placeholder?: string;
style?: any;
onPress?: () => void;
showLoadingIndicator?: boolean;
}
const OptimizedImage: React.FC<OptimizedImageProps> = ({
source,
width = 200,
height = 200,
placeholder = 'https://via.placeholder.com/200x200?text=Loading...',
style,
onPress,
showLoadingIndicator = true,
}) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [imageUri, setImageUri] = useState(placeholder);
// Memoize optimized image URI
const optimizedUri = useMemo(() => {
if (!source.uri) return placeholder;
// Add optimization parameters
const url = new URL(source.uri);
url.searchParams.set('w', width.toString());
url.searchParams.set('h', height.toString());
url.searchParams.set('q', '80'); // Quality
url.searchParams.set('f', 'webp'); // Format
return url.toString();
}, [source.uri, width, height, placeholder]);
const handleLoadStart = useCallback(() => {
setLoading(true);
setError(false);
}, []);
const handleLoadEnd = useCallback(() => {
setLoading(false);
setImageUri(optimizedUri);
}, [optimizedUri]);
const handleError = useCallback(() => {
setLoading(false);
setError(true);
setImageUri(placeholder);
}, [placeholder]);
const imageStyle = useMemo(() => [
{
width,
height,
borderRadius: 8,
},
style,
], [width, height, style]);
const ImageComponent = (
<View style={styles.container}>
<Image
source={{ uri: imageUri }}
style={imageStyle}
onLoadStart={handleLoadStart}
onLoadEnd={handleLoadEnd}
onError={handleError}
resizeMode="cover"
/>
{loading && showLoadingIndicator && (
<View style={[styles.loadingContainer, { width, height }]}>
<ActivityIndicator size="small" color="#2196F3" />
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
{error && (
<View style={[styles.errorContainer, { width, height }]}>
<Text style={styles.errorText}>Failed to load</Text>
</View>
)}
</View>
);
if (onPress) {
return (
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
{ImageComponent}
</TouchableOpacity>
);
}
return ImageComponent;
};
const styles = StyleSheet.create({
container: {
position: 'relative',
},
loadingContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.1)',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
},
loadingText: {
marginTop: 8,
fontSize: 12,
color: '#666',
},
errorContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(244, 67, 54, 0.1)',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
},
errorText: {
fontSize: 12,
color: '#f44336',
textAlign: 'center',
},
});
export default OptimizedImage;Test your understanding of this topic:
Reduce app bundle size and improve download/install times for React Native applications
Content by: Pratik Keshvala
React Native Developer
Implement code splitting and lazy loading to reduce initial bundle size and improve app startup time.
Use tree shaking to eliminate unused code and reduce bundle size in production builds.
import React, { useState, lazy, Suspense } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native';
// Lazy load heavy components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const ChartComponent = lazy(() => import('./ChartComponent'));
const BundleOptimizedApp = () => {
const [activeTab, setActiveTab] = useState('home');
const renderContent = () => {
switch (activeTab) {
case 'home':
return <HomeContent />;
case 'charts':
return (
<Suspense fallback={<LoadingSpinner />}>
<ChartComponent />
</Suspense>
);
case 'heavy':
return (
<Suspense fallback={<LoadingSpinner />}>
<HeavyComponent />
</Suspense>
);
default:
return <HomeContent />;
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Bundle Optimized App</Text>
<View style={styles.tabContainer}>
<TouchableOpacity
style={[styles.tab, activeTab === 'home' && styles.activeTab]}
onPress={() => setActiveTab('home')}
>
<Text style={[styles.tabText, activeTab === 'home' && styles.activeTabText]}>
Home
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'charts' && styles.activeTab]}
onPress={() => setActiveTab('charts')}
>
<Text style={[styles.tabText, activeTab === 'charts' && styles.activeTabText]}>
Charts
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'heavy' && styles.activeTab]}
onPress={() => setActiveTab('heavy')}
>
<Text style={[styles.tabText, activeTab === 'heavy' && styles.activeTabText]}>
Heavy
</Text>
</TouchableOpacity>
</View>
<View style={styles.contentContainer}>
{renderContent()}
</View>
</View>
);
};
const HomeContent = () => (
<View style={styles.homeContainer}>
<Text style={styles.homeTitle}>Welcome to Optimized App</Text>
<Text style={styles.homeDescription}>
This app uses code splitting and lazy loading to optimize bundle size.
</Text>
</View>
);
const LoadingSpinner = () => (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#2196F3" />
<Text style={styles.loadingText}>Loading component...</Text>
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 20,
color: '#333',
},
tabContainer: {
flexDirection: 'row',
marginBottom: 20,
backgroundColor: 'white',
borderRadius: 8,
padding: 4,
},
tab: {
flex: 1,
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 6,
alignItems: 'center',
},
activeTab: {
backgroundColor: '#2196F3',
},
tabText: {
fontSize: 14,
fontWeight: '500',
color: '#666',
},
activeTabText: {
color: 'white',
},
contentContainer: {
flex: 1,
backgroundColor: 'white',
borderRadius: 8,
padding: 20,
},
homeContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
homeTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
marginBottom: 15,
textAlign: 'center',
},
homeDescription: {
fontSize: 16,
color: '#666',
textAlign: 'center',
lineHeight: 24,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 10,
fontSize: 16,
color: '#666',
},
});
export default BundleOptimizedApp;Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Continue to Module 10