Image Optimization
Optimize images for better performance and reduced memory usage in React Native applications. 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
Image Formats
Choose appropriate image formats (WebP, PNG, JPEG) and implement responsive images for different screen densities.. 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
Image Optimization Strategies
- Use WebP format for better compression — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Implement lazy loading for images — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Resize images to appropriate dimensions — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Use placeholder images during loading — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Implement progressive image loading — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Cache images locally to reduce network requests — a critical concept in cross-platform mobile development that you will use frequently in real projects
Lazy Loading
Implement lazy loading for images to improve initial load time and 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
Optimized Image Component
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;