How To Cache Images - React Native Expo (Managed)
Caching images in React Native can be easy, even if you are using Expo’s managed workflow. The problem many devs run into is that React Native only supports caching images on IOS out of the box.
Other popular community packages that work on Android contain native code, and as such don’t work with Expo’s managed workflow. For this reason, I open-sourced the code I’m using on my latest project. Behold, react-native-expo-cached-image!
Quick Start
Install the module:
yarn add react-native-expo-cached-image
Import the component:
import CachedImage from "react-native-expo-cached-image";
Use the component in a render() method:
<CachedImage
isBackground
source={{ uri: 'https://www.boot.dev/wp-content/uploads/2019/05/QVault-app.png' }}
/>
The CachedImage component has the same props and API as React Native’s Image and ImageBackground components. To use CachedImage as a background image, just pass in the isBackground prop:
<CachedImage
isBackground
source={{ uri: 'https://www.boot.dev/wp-content/uploads/2019/05/QVault-app.png' }}
/>
What Is It Doing?
CachedImage keeps it simple. It downloads the image to the user’s local filesystem using the SHA-256 hash of the URI. Then, on subsequent renders and app uses, it loads the image from the filesystem if it exists. This saves the user from using unnecessary data and experiencing slow load times.
Tip: To bust the cache, you can append a query string or anchor text to the URI.
Code

As of writing, here is the code, feel free to just copypasta it if you don’t want to install the dependency:
import React, { Component } from "react";
import { View, Image, ImageBackground } from "react-native";
import * as FileSystem from "expo-file-system";
import * as Crypto from "expo-crypto";
export default class CachedImage extends Component {
state = {
imgURI: "",
};
async componentDidMount() {
const filesystemURI = await this.getImageFilesystemKey(
this.props.source.uri,
);
await this.loadImage(filesystemURI, this.props.source.uri);
}
async componentDidUpdate() {
const filesystemURI = await this.getImageFilesystemKey(
this.props.source.uri,
);
if (
this.props.source.uri === this.state.imgURI ||
filesystemURI === this.state.imgURI
) {
return null;
}
await this.loadImage(filesystemURI, this.props.source.uri);
}
async getImageFilesystemKey(remoteURI) {
const hashed = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
remoteURI,
);
return `${FileSystem.cacheDirectory}${hashed}`;
}
async loadImage(filesystemURI, remoteURI) {
try {
// Use the cached image if it exists
const metadata = await FileSystem.getInfoAsync(filesystemURI);
if (metadata.exists) {
this.setState({
imgURI: filesystemURI,
});
return;
}
// otherwise download to cache
const imageObject = await FileSystem.downloadAsync(
remoteURI,
filesystemURI,
);
this.setState({
imgURI: imageObject.uri,
});
} catch (err) {
console.log("Image loading error:", err);
this.setState({ imgURI: remoteURI });
}
}
render() {
return (
<View>
{this.props.isBackground ? (
<ImageBackground
{...this.props}
source={this.state.imgURI ? { uri: this.state.imgURI } : null}
>
{this.props.children}
</ImageBackground>
) : (
<Image
{...this.props}
source={this.state.imgURI ? { uri: this.state.imgURI } : null}
/>
)}
</View>
);
}
}
Related Articles
JavaScript With Statement Explained – A Deep Dive
Jan 15, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
JavaScript’s built-in with statement specifies the default object for the given property and gives us a shorthand for writing long object references. More precisely, it adds the given object to the head of the scope chain.
JavaScript Map Function Explained - A Deep Dive
Jan 12, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
The built-in JavaScript map function returns a new array, where each element in the new array is the result of the corresponding element in the old array after being passed through a callback function. Later in the article, we’ll do a deep dive into some more advanced concepts regarding the map function and its uses.
Which Method of Iteration in JavaScript is Fastest?
Nov 08, 2019 by Lane Wagner - Boot.dev co-founder and backend engineer
There are many ways to traverse an array in Javascript. In this benchmark, we will look at five different ways and the pros and cons of each. Keep in mind that these benchmarks were run in a Chrome browser on Codepen.io. Results will vary by browser/interpreter.
Singletons in ES6 - The Good, The Bad, The Ugly
Nov 04, 2019 by Lane Wagner - Boot.dev co-founder and backend engineer
Singletons are fairly controversial as far as I can tell, especially in JavaScript programming. Let’s take a look at what they are, when to (maybe) use them, and when not to.