Memory Management
Learn memory management best practices to prevent memory leaks and optimize app performance. This is a foundational concept in cross-platform mobile development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React Native experience. Take your time with each section and practice the examples
Memory Leaks
Identify and prevent common memory leaks in React Native applications including event listeners, timers, and component references.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Common Memory Leak Sources
- Event listeners not removed on component unmount — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Timers and intervals not cleared — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Subscriptions not cancelled — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Large objects held in state unnecessarily — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Circular references between objects — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Images not properly released from memory — a critical concept in cross-platform mobile development that you will use frequently in real projects
Memory Optimization
Implement strategies like lazy loading, image optimization, and proper cleanup to reduce memory usage.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Memory Management Example
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;