VOOZH about

URL: https://blog.logrocket.com/caching-images-react-native-tutorial-with-examples/

⇱ Caching images in React Native: A tutorial with examples - LogRocket Blog


2021-07-07
2138
#react native
Nitish Sharma
57893
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

From social media services, to rideshare apps, to blogging platforms, images hold quite an important position for data representation. They play a large role in enhancing the user experience and are indeed vital to the user-friendliness of your app.

👁 Caching Images in React Native: A Tutorial With Examples

From a developer point of view, loading remote images isn’t a huge pain point in React Native. But even with the best of the optimizations added to the Component, be it a class or functional component, image loading and rerendering can slow down the app, which leads a laggy interface.

In this tutorial, we’ll first show you how to cache images in React Native using the react-native-fast-image library. Then, we’ll demonstrate how to build your own React Native image caching component from scratch with step-by-step instructions and detailed examples.

Here’s what we’ll cover:

To follow along, you should be familiar with the basics of React Native — e.g., JSX, components (class as well as functional), and styling. You can simply copy and paste the code blocks from this guide, but I would suggest reading through the whole tutorial for better understanding.

🚀 Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

What is image caching in React Native?

Caching is a great way to solve issues associated with loading and rerendering images from remote endpoints. Image caching essentially means downloading an image to the local storage in the app’s cache directory (or any other directory that is accessible to the app) and loading it from local storage next time the image loads.

There are a few ways to approach image caching in React Native. If you’re building a bare-bones React Native app, there’s a wonderful component available that handles all your image caching automatically without writing any extra code called React Native FastImage. Or, if you’re using Expo or working on a more complex project, you might decide to build your own image caching component from scratch.

What is react-native-fast-image?

react-native-fast-image is a performant React Native component for loading images. FastImage aggressively caches all loaded images. You can add your own request auth headers and preload images. react-native-fast-image even has GIF caching support.

To start using React Native FastImage, first import the FastImage component:

import FastImage from 'react-native-fast-image';

Below is the basic implementation of the FastImage component:

<FastImage
 style={{ width: 200, height: 200 }}
 source={{uri: 'https://unsplash.it/400/400?image=1'}}
/>

Here’s a preview of what this looks like:

👁 react-native-fast-image Example

Using the FastImage component: A practical example

Let’s look at a basic example of using the FastImage component with a few props:

<FastImage
 style={{ width: 200, height: 200 }}
 source={{
 uri: '...image...url...',
 headers: { Authorization: 'someAuthToken' },
 priority: FastImage.priority.normal,
 }}
 resizeMode={FastImage.resizeMode.contain}
/>

As you can see, this example is almost the same as the basic React Native image component, but on steroids. Let’s break down the code in finer detail.

  • source contains the image, its headers, and more
  • uri represents the path of the image you want to load
  • headers represent the headers you might need (auth token in the example above)
  • priority signifies the priority of the images — e.g., if you need to load a certain image first, you can set the priority to FastImage.priority.high

React native cache property

cache is where things get exciting. You’re probably familiar with uri, header, and others props of the Image component. It’s the same for FastImage with only slight changes. cache is what you’d use to change the behavior of image caching and image loading.

There are three properties you can use in cache:

  1. FastImage.cacheControl.immutable is the default property for the FastImage component. The image only caches or updates if the uri is changed
  2. FastImage.cacheControl.web enables you to configure the FastImage component to cache images like the browser, using headers and the normal caching procedure
  3. FastImage.cacheControl.cacheOnly enables you to restrict the FastImage component to fetch from already-cached images — i.e., without making any new network requests.

Here’s an example of an image with the cache property:

<FastImage
 style={{ width: 200, height: 200 }}
 source={{
 uri: 'https://unsplash.it/400/400?image=1',
 cache: FastImage.cacheControl.cacheOnly
 }}
/>

To state the benefit simply, if you can maintain a local database of images that are loaded once, you can us this cache property to save on bandwidth costs by fetching cached images from device storage.

How to build an image caching component from scratch

FastImage is great for bare-bones React Native projects, but if you’re using Expo or have needs that react-native-fast-image can’t meet, you may want to write your own image caching component.

Before building your own image caching component, it’s crucial to understand the basics of caching an image. Let’s review: To cache an image is to store it in the local storage of the device so that it can be accessed quickly next time around without any network requests.

To to cache an image, we need the network URI, or URL of that image, and a string identifier to fetch it the next time around. We need a unique identifier for each resource because multiple images can have the same name, which can be a problem when differentiating between the local cache and images with redundant names.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

For this guide, I’ll assume that you’re either building your app using expo or using expo-file-system via unimodules in bare React Native.

Our component should take in three basic props:

  1. source for the URI of the network image
  2. cacheKey for the unique identifier for an image
  3. style for styling the image component:
    let image = {
     uri: "https://post.medicalnewstoday.com/wp-content/uploads/sites/3/2020/02/322868_1100-800x825.jpg",
     id: "MFdcdcdjvCDCcnh", //the unique id that you can store in your local db
     };
    <CustomFastImage
     source={{ uri: image.uri }}
     cacheKey={image.id}
     style={{ width: 200, height: 200 }}
    />

For the logic of our custom image caching component, we’ll import expo-file-system:

import * as FileSystem from "expo-file-system";

First, we need to create a new local path for our remote image using the cacheKey (unique ID) to check whether it already exists in the local cache and, if not, download it.

We need to initialize the props we’re going to receive:

const CustomFastImage = (props) => {
 const {
 source: { uri },
 cacheKey,
 style,
 } = props;
...

And the function to get the extension of the image from uri:

function getImgXtension(uri) {
 var basename = uri.split(/[\\/]/).pop();
 return /[.]/.exec(basename) ? /[^.]+$/.exec(basename) : undefined;
}

This function returns an array of extensions. You can just use the first item of the array.

Then, we’ll call this function to get the extension from the useEffect Hook from the component and use the returned extension to create the local cache path for the image:

useEffect(() => {
 async function loadImg() {
 let imgXt = getImgXtension(uri);
 if (!imgXt || !imgXt.length) {
 Alert.alert(`Couldn't load Image!`);
 return;
 }
 const cacheFileUri = `${FileSystem.cacheDirectory}${cacheKey}.${imgXt[0]}`;
 }
 loadImg();
 }, []);

FileSystem.cacheDirectory is the path of the cache directory. You can change this according to your own preference.

Now, we need to check whether the image at this path already exists using a function like this:

async function findImageInCache(uri) {
 try {
 let info = await FileSystem.getInfoAsync(uri);
 return { ...info, err: false };
 } catch (error) {
 return {
 exists: false,
 err: true,
 msg: error,
 };
 }
}

Add this to useEffect > loadImg():

useEffect(() => {
 async function loadImg() {
 let imgXt = getImgXtension(uri);
 if (!imgXt || !imgXt.length) {
 Alert.alert(`Couldn't load Image!`);
 return;
 }
 const cacheFileUri = `${FileSystem.cacheDirectory}${cacheKey}.${imgXt[0]}`;
 let imgXistsInCache = await findImageInCache(cacheFileUri);
 }
 loadImg();
 }, []);

Now we need a function to cache the image to local storage if it is not already cached and return the desired output:

async function cacheImage(uri, cacheUri, callback) {
 try {
 const downloadImage = FileSystem.createDownloadResumable(
 uri,
 cacheUri,
 {},
 callback
 );
 const downloaded = await downloadImage.downloadAsync();
 return {
 cached: true,
 err: false,
 path: downloaded.uri,
 };
 } catch (error) {
 return {
 cached: false,
 err: true,
 msg: error,
 };
 }
}

We’ll also need a const with the useState() Hook to store the path of the image once loaded:

const [imgUri, setUri] = useState("");

For a better user experience, you can add an ActivityIndicator (or any loading indicator of that sort according to your preference) and implement it according to the change in the imgUri state.

return (
 <>
 {imgUri ? (
 <Image source={{ uri: imgUri }} style={style} />
 ) : (
 <View
 style={{ ...style, alignItems: "center", justifyContent: "center" }}
 >
 <ActivityIndicator size={33} />
 </View>
 )}
 </>
);

In the useEffect Hook, we need to update the imgUri when the image is cached or already available in the local storage:

 useEffect(() => {
 async function loadImg() {
 let imgXt = getImgXtension(uri);
 if (!imgXt || !imgXt.length) {
 Alert.alert(`Couldn't load Image!`);
 return;
 }
 const cacheFileUri = `${FileSystem.cacheDirectory}${cacheKey}.${imgXt[0]}`;
 let imgXistsInCache = await findImageInCache(cacheFileUri);
 if (imgXistsInCache.exists) {
 console.log("cached!");
 setUri(cacheFileUri);
 } else {
 let cached = await cacheImage(uri, cacheFileUri, () => {});
 if (cached.cached) {
 console.log("cached NEw!");
 setUri(cached.path);
 } else {
 Alert.alert(`Couldn't load Image!`);
 }
 }
 }
 loadImg();
 }, []);

Here’s the complete code for the CustomFastImage component we’ve built:

import React, { useEffect, useRef, useState } from "react";
import { Alert, Image, View, ActivityIndicator } from "react-native";
import * as FileSystem from "expo-file-system";
function getImgXtension(uri) {
 var basename = uri.split(/[\\/]/).pop();
 return /[.]/.exec(basename) ? /[^.]+$/.exec(basename) : undefined;
}
async function findImageInCache(uri) {
 try {
 let info = await FileSystem.getInfoAsync(uri);
 return { ...info, err: false };
 } catch (error) {
 return {
 exists: false,
 err: true,
 msg: error,
 };
 }
}
async function cacheImage(uri, cacheUri, callback) {
 try {
 const downloadImage = FileSystem.createDownloadResumable(
 uri,
 cacheUri,
 {},
 callback
 );
 const downloaded = await downloadImage.downloadAsync();
 return {
 cached: true,
 err: false,
 path: downloaded.uri,
 };
 } catch (error) {
 return {
 cached: false,
 err: true,
 msg: error,
 };
 }
}
const CustomFastImage = (props) => {
 const {
 source: { uri },
 cacheKey,
 style,
 } = props;
 const isMounted = useRef(true);
 const [imgUri, setUri] = useState("");
 useEffect(() => {
 async function loadImg() {
 let imgXt = getImgXtension(uri);
 if (!imgXt || !imgXt.length) {
 Alert.alert(`Couldn't load Image!`);
 return;
 }
 const cacheFileUri = `${FileSystem.cacheDirectory}${cacheKey}.${imgXt[0]}`;
 let imgXistsInCache = await findImageInCache(cacheFileUri);
 if (imgXistsInCache.exists) {
 console.log("cached!");
 setUri(cacheFileUri);
 } else {
 let cached = await cacheImage(uri, cacheFileUri, () => {});
 if (cached.cached) {
 console.log("cached NEw!");
 setUri(cached.path);
 } else {
 Alert.alert(`Couldn't load Image!`);
 }
 }
 }
 loadImg();
 return () => (isMounted.current = false);
 }, []);
 return (
 <>
 {imgUri ? (
 <Image source={{ uri: imgUri }} style={style} />
 ) : (
 <View
 style={{ ...style, alignItems: "center", justifyContent: "center" }}
 >
 <ActivityIndicator size={33} />
 </View>
 )}
 </>
 );
};
export default CustomFastImage;

Other means of image caching in React Native

We have gone through the two methods of caching images in React Native, but, there are other ways for caching, I mean it’s programming, you can build your own means of doing stuff, but we are going to discuss two more methods, that allow us to cache images in a React Native app.

React Native’s inbuilt image component

Most new developers miss out on the functionalities that React Native provides by default. One of those functionalities is caching images using the prefetch() method of the Image component. Prefetch, as the name suggests, fetches the image from the remote server and stores it in the local device’s storage for faster loads. The basic usage of prefetch is:

await Image.prefetch(URL_OF_AN_IMAGE)

For using this method, you might need to either add a placeholder, build a lambda condition, or build a custom component using both of these to make the user experience smooth. The key is to load the image using async/await before showing it in the renderer.

You can learn more about the Image component here.

react-native-cached-image

This is another way of caching images in React Native. It basically uses a provider, i.e., ImageCacheProvider, to which we add an array of image URLs that need to be cached by the app. The CachedImage component is used to display the image that was cached using the ImageCacheProvider. We can see the implementation below:

const imgs = [url1, url2, url3, ...];
const ImgExample = () => {
return (
 <ImageCacheProvider
 urlsToPreload={imgs}
 onPreloadComplete={() => {
 console.log('You can use this callback to show the CachedImage component.');
 })
 >
 <CachedImage source={{uri: imgs[0]}}/>
 <CachedImage source={{uri: imgs[1]}}/>
 <CachedImage source={{uri: imgs[2]}}/>
 </ImageCacheProvider>
);
}

This module also contains ImageCacheManager, which can be used to delete the image from the cache using various methods available. You can check out the whole module here.

N.B., the last update of this components was released in 2017, which tends to make a module unreliable. Use with caution.

Conclusion

In this tutorial, we covered everything you need to know about image caching in React Native. We went over how to use react-native-fast-image to cache images in React Native as well as how to build your own image caching component from scratch.

For next steps, you might consider adding animations, loading indicators, and other bells and whistles to the component. You could also add a progress indicator or better a callback function using the FileSystem API.

LogRocket: Instantly identify and recreate issues in your React Native apps

👁 Image

LogRocket's Galileo AI watches sessions for you and and surfaces the technical and usability issues holding back your React Native apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your React Native apps — try LogRocket for free.

👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

Debug Next.js apps with AI agents and next-browser

Learn how next-browser gives AI agents runtime context for debugging Next.js apps, including React props, hydration, PPR, forms, and performance.

👁 Image
Emmanuel John
Jun 17, 2026 ⋅ 9 min read

Stop hardcoding LLM SDKs: Dynamic LLM routing with OpenRouter and Next.js

Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.

👁 Image
Chizaram Ken
Jun 16, 2026 ⋅ 13 min read

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension — no new framework required.

👁 Image
Ikeh Akinyemi
Jun 12, 2026 ⋅ 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo — with email/password login, Google OAuth, session persistence, and protected routes.

👁 Image
Chinwike Maduabuchi
Jun 9, 2026 ⋅ 13 min read
View all posts

Would you be interested in joining LogRocket's developer community?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now