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.
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:
react-native-fast-image
?FastImage
component: A practical examplecache
propertyTo 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.
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.
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:
FastImage
component: A practical exampleLet’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 moreuri
represents the path of the image you want to loadheaders
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
cache
propertycache
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
:
FastImage.cacheControl.immutable
is the default property for the FastImage
component. The image only caches or updates if the uri
is changedFastImage.cacheControl.web
enables you to configure the FastImage
component to cache images like the browser, using headers and the normal caching procedureFastImage.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.
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.
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:
source
for the URI of the network imagecacheKey
for the unique identifier for an imagestyle
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;
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.
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.
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 is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in 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.
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 nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
One Reply to "Caching images in React Native: A tutorial with examples"
I am building an app which contains lot of images. I want to cache the images till the size of overall cached images reaches a particular size if the size exceeds then delete some images like oldest saved image will get deleted first.How to implement the size and deletion part. Till now i am able to implement the only caching part.