When you’re ready to add visuals to your React Native app, your journey begins with the core <Image> component. Think of it as the fundamental building block for every picture, icon, or background you want to display. It’s the bridge that connects your app’s code to the device's screen, letting you render everything from local icons to photos pulled from a server.
Getting comfortable with this component is the first real step toward creating a visually engaging mobile experience.
Building Your Foundation With The Core Image Component
Every developer starts in the same place: the built-in <Image> component. It’s a deceptively simple tool that handles the complex work of displaying images consistently across both iOS and Android.

To get you started, here is a quick reference table of the most essential props you'll be using with the <Image> component.
Essential Props For The React Native Image Component
| Prop | Type | Description |
|---|---|---|
source | Object or Number | Specifies the image to display. Accepts require() for local assets or an object { uri: '...' } for network images. |
style | Style Object | Defines the image's dimensions (width, height) and other layout properties. This is required for network images. |
resizeMode | String | Controls how the image fits its container. Common values are cover, contain, stretch, repeat, and center. |
defaultSource | Object or Number | Shows a static image (often a local placeholder) while the network source image is loading. |
onLoad | Function | A callback function that fires once the image has successfully loaded. |
onError | Function | A callback that fires if the image fails to load, providing an error object. |
These props form the backbone of image handling in React Native. Mastering them will solve the vast majority of your initial layout and performance challenges.
The Source Prop Unpacked
The single most important prop you'll ever use is source. This prop is your way of telling the <Image> component what to show and where to find it. It's flexible and handles two very different scenarios:
- Local Images: For static assets that are part of your app bundle (like logos, icons, or tab bar graphics), you’ll use the
require()function. The path is resolved when you build the app, so the image is guaranteed to be available, even offline. - Network Images: For dynamic content pulled from the web (like user avatars or product photos from an API), you’ll pass an object with a
urikey:{ uri: 'https://...' }.
For example, showing a local logo is as simple as <Image source={require('./assets/logo.png')} />. On the other hand, a user’s profile picture from a server would look like this: <Image source={{ uri: 'https://example.com/user_avatar.jpg' }} />. Knowing which one to use is key to managing your assets properly.
The Most Common Mistake: Sizing Your Images
Just about every developer hits this wall: you add an <Image> component, point it to a valid URL, and… nothing shows up. The culprit is almost always the same: you forgot to set a width and height.
By default, a React Native
<Image>component has dimensions of 0x0. If you don't give it explicit dimensions in its style, it has no space to render and will be completely invisible.
Think of it like a digital picture frame that’s been collapsed. The picture is there, but the frame has no size. You have to give it a physical area on the screen to occupy.
// This image will NOT appear on screen
<Image source={{ uri: '…' }} />
// This image WILL be visible
<Image
source={{ uri: '…' }}
style={{ width: 100, height: 100 }}
/>
This is especially critical for network images, since React Native can't know the image's dimensions before it finishes downloading. While the bundler might be able to infer the size of local images you require(), it's a terrible habit to rely on that. Always define your dimensions to create a stable, predictable layout.
Controlling Image Fit With ResizeMode
Once your image is visible, the next step is to control how it behaves inside its container. The resizeMode prop is your tool for this, preventing your images from looking stretched, squashed, or poorly cropped.
You'll find yourself using two options more than any others:
cover: This mode scales the image up or down until it completely fills the container'swidthandheight. If the aspect ratios don't match, parts of the image will be cropped. It’s perfect for full-screen backgrounds or circular avatars where filling the space is the top priority.contain: This mode scales the image until it fits perfectly inside the container, without any cropping. It ensures the entire image is visible, but you might end up with empty space (letterboxing) if the aspect ratios differ. This is the best choice for logos or product images where you must show the whole thing.
Tackling the Biggest Performance Killer: Image Caching
The core <Image> component is fine for getting started, but it has a massive blind spot that can cripple a real-world app: caching. Images are almost always the heaviest assets you'll load, and fetching them over and over again is a surefire way to kill performance.

Think about a social media feed. If a user's profile picture has to be re-downloaded every single time it scrolls into view, you're not just creating a slow, jerky experience. You're also burning through their data plan and draining their battery. To build a truly professional react native image experience, you need to think smarter.
Where the Default Image Component Fails
React Native's built-in <Image> component does have some caching, but it's basic and often unreliable—especially on Android. It simply wasn't built for the kind of aggressive, persistent caching that modern apps need to feel fast.
When your app displays a long list or a grid with dozens of remote images, the default component's limitations become painfully obvious. We’ve seen this firsthand. Unoptimized images can easily drag frame rates down from a buttery-smooth 60 FPS to a choppy 20-30 FPS on decent Android devices. In gallery-heavy screens, the lack of proper caching can cause memory usage to spike by up to 200MB as it re-downloads assets.
This is exactly why dedicated caching libraries exist. They create a smart local storage layer for your images. An image is downloaded once, saved to the device's disk, and then loaded instantly every other time it's needed.
Introducing React Native Fast Image
The community's go-to solution for this problem is, without a doubt, react-native-fast-image. This isn't just another JavaScript wrapper. It's a bridge to the most powerful, battle-tested native image-loading libraries available: Glide on Android and SDWebImage on iOS. These are the same libraries that power many of the top native apps in the App Store and Google Play.
In short,
react-native-fast-imagegives your app the same industrial-strength image performance as an app written in Swift or Kotlin. It completely bypasses React Native's default image handling to do things the native, high-performance way.
Getting started is surprisingly simple. Once you've installed the library, you just swap your <Image> components for <FastImage>. The API is intentionally similar, making the migration incredibly smooth.
Here’s a quick look at the difference in code:
Standard Image Component:
import { Image, StyleSheet } from 'react-native';
const MyComponent = () => (
<Image
style={styles.image}
source={{
uri: 'https://your-image-url.com/image.jpg',
}}
/>
);
const styles = StyleSheet.create({
image: {
width: 100,
height: 100,
},
});
FastImage Component:
import FastImage from 'react-native-fast-image';
import { StyleSheet } from 'react-native';
const MyComponent = () => (
<FastImage
style={styles.image}
source={{
uri: 'https://your-image-url.com/image.jpg',
priority: FastImage.priority.normal,
}}
resizeMode={FastImage.resizeMode.contain}
/>
);
const styles = StyleSheet.create({
image: {
width: 100,
height: 100,
},
});
As you can see, the props are almost identical. But FastImage also unlocks powerful new controls like priority, which lets you tell the library to load critical images before less important ones. If you want a more detailed walkthrough, check out our guide on how to use React Native Fast Image.
Squeeze More Performance with Modern Formats
Beyond caching, you can get another huge performance boost by using modern image formats like WebP. Compared to old-school JPEGs and PNGs, WebP offers fantastic quality at a fraction of the file size.
- Lossy WebP: Can be 25-34% smaller than a comparable JPEG image.
- Lossless WebP: Can be 26% smaller than a PNG.
The best part? Because react-native-fast-image uses native libraries, it supports WebP on both iOS and Android right out of the box. By setting up your server or CDN to serve WebP files, you'll dramatically cut down download times and data usage.
Combining aggressive caching with modern formats is the one-two punch for delivering a blazing-fast image experience in any React Native app.
Creating A Polished User Experience While Images Load
Perceived performance is often more important than raw speed. Nothing screams "slow app" quite like a blank white box where an image should be, even if it only lasts for a second. That jarring gap can make an otherwise fast app feel clunky and unfinished.
The trick is to gracefully manage that loading state. We can turn that brief waiting period into a smooth, almost unnoticeable part of the experience.

Refining how your react native image components appear is what separates a decent app from a professional one. These techniques are all about controlling the user’s perception, making your UI feel slick even when the network isn't cooperating.
Implementing Progressive Image Loading
A fantastic way to handle this is with progressive loading, which you’ve likely seen as a "blur-up" effect. The idea is simple: instead of showing nothing, you first load a tiny, heavily blurred thumbnail of the final image. It’s so small that it loads almost instantly, giving the user immediate visual feedback.
Then, while the user sees the blurred placeholder, the full-resolution image downloads in the background. Once it’s ready, it elegantly fades in, creating a seamless transition from blurry to sharp. This completely gets rid of the jarring "pop-in" effect.
You can pull this off by stacking two <Image> components.
- The Thumbnail: Start with a very small version of the image, maybe just 20 pixels wide. Use the
blurRadiusprop on this component to create that hazy, out-of-focus look. - The Full Image: This is your high-quality image. You'll use its
onLoadevent to know when it has finished downloading, then trigger an animation to fade its opacity from0to1.
This approach makes it feel like the image is simply coming into focus, which is a much more sophisticated experience than a loading spinner.
Supercharge Lists with Lazy Loading
If your app has long, scrollable lists—think of an e-commerce product feed or a social media timeline—loading every single image upfront is a performance nightmare. It drains memory, burns through the user's data plan, and can make your entire app grind to a halt. The answer is lazy loading.
Lazy loading is a simple but powerful optimization: you only load images when they are about to scroll into view. It's a must-have for any app with list-based content.
Rather than fetching dozens or hundreds of images when the screen first loads, your app only requests the ones that are currently visible (or just off-screen). This dramatically cuts down on initial memory usage and network requests, making your app feel snappy from the get-go.
Thankfully, React Native’s <FlatList> component is built for this. Props like onEndReached (to fetch the next batch of data) and windowSize (to control how many items are rendered outside the visible area) give you the tools to build a highly efficient, lazy-loading list with minimal fuss.
Ditch the Spinner for Skeleton Loaders
The classic loading spinner has a major downside: it constantly reminds the user, "You are waiting." A more modern and user-friendly alternative is the skeleton loader. These are animated placeholders that mimic the size and shape of the content that's about to load.
Instead of a generic spinner, the user sees a shimmering gray box where the image will go, a gray bar for the title, and a few smaller lines for the description. This approach is brilliant for a couple of reasons:
- It Reduces Perceived Wait Time: The user sees the page structure immediately, which makes it feel like the content is already partially loaded. The wait feels shorter.
- It Prevents UI Jumps: Because the skeleton placeholder occupies the exact same space as the final content, the layout doesn't suddenly shift or reflow as images pop into place.
You can easily implement this effect with libraries like react-native-skeleton-placeholder. By filling empty space with these polished placeholders, you signal quality and make your app feel more alive and responsive, even while fetching data.
Animating Images Without Killing Performance
Static images are the backbone of any app, but animations are what make an interface feel alive and responsive. The challenge, though, is that poorly executed animations can lead to a laggy, frustrating experience for your users. Our goal is always a silky-smooth 60 frames per second (FPS), and I'll walk you through exactly how to get there.
While you could just drop in an animated GIF for a simple loop, they tend to be heavy and offer zero control. For high-quality UI animations involving images, the secret isn't about the image format—it's all about how you animate it.
The Performance Trap of Animating Layout Properties
When you want to make an image grow or shrink, your first instinct is probably to animate its width and height. It seems logical, but in practice, it’s a one-way ticket to a performance nightmare.
Every time you change a layout property like width or height, you kick off a chain reaction. The JavaScript thread has to communicate that change over the bridge, forcing the native UI thread to recalculate the entire layout, repaint components, and then draw the result. This is a heavy-duty process that has to happen on every single frame of the animation.
Animating an Image component's size directly forces the device to re-crop and scale the original bitmap for each frame. On modern phones, this alone can tank your frame rate from a smooth 60 FPS to below 40. By switching to a better technique, you can cut re-renders by as much as 70% and see scroll performance jump from 45 to 59 FPS on lower-end devices. For more, check out the official React Native performance documentation.
This constant back-and-forth clogs the main thread, causing the dreaded "jank" where your animation stutters and skips frames. The UI thread simply can't keep up.
The Solution: Animate Transforms with the Native Driver
The professional approach is to completely sidestep layout properties. Instead, we animate the transform property using scale, translateX, and translateY.
The real breakthrough comes when you pair this with useNativeDriver: true in React Native's Animated API. Think of this as an "express lane" for your animation. It tells React Native to hand off the entire animation from the busy JavaScript thread directly to the native UI thread.
Once the animation is offloaded, the native side handles everything. It calculates all the transform changes and renders them without ever needing to check back in with the JavaScript thread. This is how you achieve those buttery-smooth, 60 FPS animations that feel truly native.
To make the difference crystal clear, let's compare the two approaches.
Image Animation Performance Comparison
The table below breaks down why animating transform with the native driver is vastly superior to animating layout properties like width and height.
| Animation Method | Thread | Performance Impact | Recommendation |
|---|---|---|---|
Animating width and height | JavaScript & UI | High. Causes layout recalculation on every frame, blocking the UI thread (jank). | Avoid at all costs for any animation that needs to be smooth. |
Animating transform with useNativeDriver: true | Native UI Thread Only | Low. The animation runs independently, never blocking the JS thread. Achieves 60 FPS. | Always use this method for size, position, or rotation animations. |
The takeaway is simple: always favor transform and the native driver. Your users' devices will thank you.
If you're looking to streamline these complex animations, it's worth exploring libraries that handle this logic for you. We've covered some excellent options in our review of the top 10 React Native animation libraries.
Level Up Your Icons with SVG
For graphics like icons, logos, and simple illustrations, you can do even better than standard PNG or JPEG files. Meet Scalable Vector Graphics (SVG). Unlike raster images, which are built from a grid of pixels, SVGs are drawn from mathematical equations.
This gives them some incredible superpowers:
- Infinite Scalability: An SVG looks perfectly crisp no matter how big or small you make it. From a tiny tab icon to a full-screen background, it will never get blurry or pixelated.
- Small File Size: For most icons and logos, SVGs are significantly smaller than their raster counterparts. This helps shrink your app's final bundle size.
- Animatable Parts: You can target and animate individual paths and shapes within an SVG. This unlocks a world of creative possibilities for interactive and dynamic graphics.
The go-to library for this in the React Native world is react-native-svg. Once you have it installed, you can import your .svg files and use them just like any other React component. For any graphic that needs to be resolution-independent, SVG is the clear winner.
Navigating The Image Upload Workflow
Displaying a React Native image is one thing, but letting users upload their own is a completely different ballgame. This feature is the lifeblood of so many apps—think profile pictures, photo feeds, or sharing moments. Getting the upload workflow right isn't just a "nice-to-have"; it's critical for a great user experience.
The whole process has a few key stages: letting the user pick a photo, asking for the right permissions, getting the image ready for its journey, and finally, sending it off to your server. A hiccup at any of these points can lead to frustrated users, failed uploads, and those tricky, platform-specific bugs that we all love to debug.
Choosing The Right Image Picker
First things first, you need to give your users a way to actually select an image. Since React Native doesn’t provide a built-in tool for this, we lean on the community. Two libraries stand out as the go-to choices: react-native-image-picker and react-native-image-crop-picker.
react-native-image-picker: This is your reliable, all-in-one solution. It offers a simple, unified API to either open the device camera or browse the photo library, smoothing over many of the annoying differences between iOS and Android. For most apps, this is the perfect place to start.react-native-image-crop-picker: This library takes things a step further. On top of picking images, it comes with built-in tools for cropping, rotating, and resizing them before they're even handed back to your app. It's an absolute lifesaver for things like profile pictures, where you need to enforce a specific size or aspect ratio.
If you're looking for a step-by-step walkthrough, check out our guide on how to pick images from the camera and gallery in a React Native app, which is packed with code examples.
Handling Permissions Gracefully
Before your app can peek into a user's photo library or access their camera, you have to ask for permission. This isn't optional—both iOS and Android strictly enforce it. If you try to launch a picker without getting permission first, your app will either crash or throw an error.
The best practice here is to ask for permission right when you need it—what we call "just-in-time" permission. For example, when a user taps the "Add Photo" button, that's your cue to trigger the permission prompt. If they say yes, fantastic, open the picker. If they say no, you should explain why the feature needs access and show them how to enable the permission later in their device settings.
This flow chart visualizes the path from a poorly optimized animation to a silky-smooth one.

This process flow shows how shifting from direct layout property animation to using transform with the native driver leads to a much better, high-performance animation.
Preparing The Image For Upload
So, the user has picked an image. The library hands you an object with details like the image URI, width, and height. It's tempting to just grab that URI and send it straight to your server, but don't do it! A single photo from a modern phone can easily be 5-10 MB, which is way too big for a quick upload.
Uploading massive, uncompressed images is a classic performance killer. It burns through the user's data plan, makes uploads painfully slow, and puts a heavy load on your server. Always compress images on the client side before sending them.
This is where a library like react-native-image-crop-picker really shines, as it can handle compression for you. You can often shrink a file's size by 80-90% with almost no noticeable drop in quality.
Once the image is compressed, the last step is to package it for your server, usually with a FormData object. You’ll append the image data itself, along with its filename and type. A common gotcha here is the file path; iOS and Android can return different URI formats. You'll often need to clean up the path, like stripping the file:// prefix on Android, to avoid issues.
With your FormData ready, you can use a library like fetch or axios to post it to your API endpoint. And that's it! You've successfully guided the user's image from their device all the way to your server.
Your Questions About React Native Images Answered
Sooner or later, every React Native developer runs into the same handful of image problems. From images that stubbornly refuse to appear to figuring out the best way to handle backgrounds, certain questions pop up over and over again.
This section is a quick-fire Q&A for those common headaches. I've been there, I've fixed them, and here are the practical answers to get you unstuck and back to coding.
Why Is My React Native Image Not Showing Up?
This is the "hello, world!" of React Native image issues, and we've all experienced it. Nine times out of ten, the culprit is simple: you forgot to set a width and height. Without explicit dimensions in your style prop, the <Image> component defaults to a size of 0x0, making it completely invisible.
If you've already set the dimensions and your image is still playing hide-and-seek, here’s your troubleshooting checklist:
- Source Path: For local images, triple-check your
require()path. Is it exactly correct relative to the file you're in? A simple typo is a common cause. - Remote URI: When fetching from a URL, make sure the URI is a valid, live link that starts with
https://. Try opening it in your browser to confirm it works. - Build Issues: Just added a new local image to your project? The Metro bundler might not have picked it up yet. A quick restart of Metro, or sometimes a full project rebuild, will force it to recognize the new asset.
How Do I Create A Background Image?
While you could try to wrestle a regular <Image> into submission with absolute positioning, there's a much cleaner, purpose-built tool for the job: <ImageBackground>.
Think of it as a View that can display an image. It works just like the standard <Image> component but allows you to nest other components inside it as children. Anything you place inside, like <Text> or another <View>, will automatically render right on top.
import { ImageBackground, Text, StyleSheet } from 'react-native';
const MyScreen = () => (
<ImageBackground
source={require('./assets/background.png')}
style={styles.background}
<Text>This text appears over the image.</Text>
);const styles = StyleSheet.create({
background: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
},
});
What Is The Difference Between require() And A URI Source?
This is a crucial distinction that boils down to where your image lives and when your app finds out about it. One is bundled with your app from the start, while the other is fetched on-demand from the internet.
Think of
require()as something your app already owns, packed in its suitcase. It knows exactly what it is and how big it is. An{uri: '...'}source is something the app has to order online—it won't know the size until the package arrives.
require('./image.png'): This is for your static, local images. At build time, the Metro bundler finds this image, gets its dimensions automatically, and packages it directly into your app binary. Use this for things that never change, like logos, icons, and tab bar graphics.{uri: '...'}: This is for dynamic, remote images living on a server somewhere. Your app only learns about these at runtime when it makes a network request. This is precisely why you must always provide awidthandheightfor URI-based images—React Native has no way of knowing their size beforehand.
When Should I Use react-native-fast-image?
Honestly, you should reach for react-native-fast-image for almost every remote image in a production app. The built-in <Image> component is fine for simple use cases, but its caching capabilities are very limited—especially on Android, where they're almost non-existent. This leads to images re-downloading constantly, which slows down your app and burns through users' data plans.
FastImage is the professional-grade solution. It hooks into powerful native libraries (Glide on Android, SDWebImage on iOS) to aggressively cache images on the device's disk. The result? Images load instantly after the first time, making for silky-smooth lists, galleries, and user profiles. For any react native image you're pulling from a network, FastImage is the way to go.
At React Native Coders, we love diving deep into these practical topics. We create in-depth tutorials and guides to help you build better, more professional apps. To keep learning and mastering the ecosystem, check out our latest articles at https://reactnativecoders.com.





















Add Comment