Image
The <Image> component renders local, bundled, or remote images. Get the resize mode, caching, and resolution variants right and your UI feels native; ignore them and you ship a janky grid that drops frames on every scroll.
sources, resizeMode, FastImage, caching
EXAMPLE
import { Image, ImageBackground, View, Text, ScrollView, StyleSheet } from 'react-native';
// 1) Local image — bundled with the app
<Image source={require('./assets/logo.png')} style={{ width: 120, height: 40 }} />
// React Native auto-picks logo.png / logo@2x.png / logo@3x.png based on device scale.
// 2) Remote image
<Image
source={{ uri: 'https://cdn.example.com/avatar/42.jpg' }}
style={{ width: 80, height: 80, borderRadius: 40 }}
/>
// You MUST specify width and height for network images, or layout breaks.
// 3) Multi-resolution remote images
<Image
source={{
uri: 'https://cdn.example.com/cover.jpg',
cache: 'force-cache', // iOS: prefer cache; check network only if missing
headers: { Authorization: `Bearer ${token}` },
}}
style={{ width: '100%', aspectRatio: 16 / 9 }}
/>
// 4) resizeMode — how the image fits into its box
// 'cover' default — fill the box, crop overflow (most common for hero images)
// 'contain' fit entirely inside, may leave bars
// 'stretch' stretches/squashes to exactly the box — almost never what you want
// 'center' no scaling, centered
// 'repeat' tile (iOS only)
<Image source={...} style={styles.hero} resizeMode="cover" />
<Image source={...} style={styles.thumb} resizeMode="contain" />
// 5) Placeholder + onLoad/onError
import { useState } from 'react';
function Avatar({ uri }) {
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(false);
if (error) return <View style={styles.fallback}><Text>?</Text></View>;
return (
<View>
{!loaded && <View style={[styles.avatar, styles.placeholder]} />}
<Image
source={{ uri }}
style={[styles.avatar, !loaded && styles.hidden]}
onLoad={() => setLoaded(true)}
onError={() => setError(true)}
/>
</View>
);
}
// 6) ImageBackground — children render on top of an image
<ImageBackground
source={{ uri: 'https://…/hero.jpg' }}
style={{ height: 220, justifyContent: 'flex-end', padding: 16 }}
imageStyle={{ borderRadius: 12 }}
>
<Text style={{ color: 'white', fontSize: 24, fontWeight: '600' }}>
Sale this week
</Text>
</ImageBackground>
// 7) Aspect ratio without measuring
<Image source={{ uri }} style={{ width: '100%', aspectRatio: 4 / 3 }} />
// For unknown remote dimensions, fetch them first:
import { Image as RNImage } from 'react-native';
RNImage.getSize(uri, (w, h) => setRatio(w / h));
// 8) Prefetching
import { Image } from 'react-native';
await Image.prefetch('https://cdn.example.com/next-page-hero.jpg');
await Image.queryCache(['https://cdn.example.com/a.jpg', 'https://cdn.example.com/b.jpg']);
await Image.clearMemoryCache();
await Image.clearDiskCache();
// 9) FastImage (community) — better caching, priority, progressive JPEG
import FastImage from 'react-native-fast-image';
<FastImage
style={{ width: 200, height: 200 }}
source={{
uri: 'https://cdn.example.com/large.jpg',
priority: FastImage.priority.high,
cache: FastImage.cacheControl.immutable,
}}
resizeMode={FastImage.resizeMode.cover}
onLoadStart={() => /* show spinner */ undefined}
/>
// FastImage uses native SDWebImage (iOS) / Glide (Android) under the hood —
// dramatically smoother in long lists. Drop-in for high-throughput screens.
// 10) Expo image (modern alternative)
import { Image as ExpoImage } from 'expo-image';
<ExpoImage
source={{ uri: 'https://cdn.example.com/photo.jpg' }}
style={{ width: 200, height: 200 }}
contentFit="cover"
transition={300} // crossfade in 300ms
placeholder={require('./blurhash')} // BlurHash or thumb
cachePolicy="memory-disk"
/>
// 11) Performance checklist for image-heavy lists
// ✓ Reach for FastImage or expo-image for FlatList rows
// ✓ Size images server-side to the rendered dimensions (e.g. ?w=400)
// ✓ Use BlurHash / thumbhash placeholders, not loading spinners
// ✓ Set width AND height (not just one) so layout doesn't shift
// ✓ Memoize row components and use getItemLayout for fixed-size rows
// ✓ Lazy-load below-the-fold images (initialNumToRender)
// ✓ Use windowSize to control how many rows stay mounted
// 12) Loading from local file system
import RNFS from 'react-native-fs';
const path = `${RNFS.DocumentDirectoryPath}/cached/avatar.jpg`;
<Image source={{ uri: `file://${path}` }} style={{ width: 80, height: 80 }} />
// 13) SVG
// react-native doesn't render SVG out of the box. Use react-native-svg + react-native-svg-transformer
// to import .svg files as components, or load via expo-image / WebView for static SVGs.
// 14) Common bugs
// • Network image without dimensions → renders 0x0 and 'disappears'
// • CDN URL with query params changes per render → cache miss every time → wrap with useMemo
// • resizeMode 'stretch' looks wrong almost always — use 'cover' or 'contain'
// • Image flicker on re-render → key on the URI, avoid replacing source object identity unnecessarily
// • Slow long lists → upgrade to FastImage or expo-image
// • Android low memory crash on big images → ask the server for a thumb URL, never load full-res into a 60-row list
Why it matters
For one-off images the built-in Image is fine; for any list scroll, upgrade to FastImage or expo-image. Always size images server-side to the rendered dimensions, set explicit width and height, and use a BlurHash placeholder — the layout-shift you avoid is the difference between “nice app” and “native”.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { Image } from 'react-native';
<Image
source={{ uri: 'https://picsum.photos/300' }}
style={{ width: 300, height: 300 }}
/>
Try it Yourself »
Discussion
Loading…