I recently worked on an app which required content stored online, but available offline. This content (loaded from a json file) included links to images, so I needed an easy way to show the online version if available, or else have a fallback to a previously downloaded image.

I decided the best option was to download the image and display the local version regardless. This let me put all the image checking stuff outside the render.

First up was to set up the basic structure. I put this in an external component to make it easier to reference. The states were to allow for possible issues: loading while the image download is attempted, failed for if the url is no good, and then imageuri , and width and height for once the image is loaded.

import React, { Component } from 'react'

import { View, Image, ActivityIndicator, Dimensions, Platform } from 'react-native'

import { FileSystem } from 'expo' class CachedImage extends Component {

state = {

loading: true,

failed: false,

imguri: '',

width: 300,

height: 300

} render() {

{

if (this.state.loading) {

// while the image is being checked and downloading

return();

}

}

{

if (this.state.failed) {

// if the image url has an issue

return();

}

}

// otherwise display the image

return();

}

}

export default CachedImage;

Next I needed to set up the code to download the image in componentDidMount() . There were a few possible image extensions, but I also had to deal with the json file sending non-image references, so I checked the extensions first, and if it wasn’t one of the expected ones, set a state of failed to true.

async componentDidMount() {

const extension = this.props.source.slice((this.props.source.lastIndexOf(".") - 1 >>> 0) + 2)

if ((extension.toLowerCase() !== 'jpg') && (extension.toLowerCase() !== 'png') && (extension.toLowerCase() !== 'gif')) {

this.setState({ loading: false, failed: true })

}

Following this was the code to download the file, save it to the cacheDirectory and then load it with a function. this.props.source and this.props.title were fed into the CachedImage function. I pulled the title from the external image filename so I could track it properly as the json data was updated with new images and the like.

await FileSystem.downloadAsync(

this.props.source,

`${FileSystem.cacheDirectory + this.props.title}.${ extension }`

)

.then(({ uri }) => {

// load the local image

this.loadLocal(uri);

})

.catch(e => {

console.log('Image loading error:', e);

// if the online download fails, load the local version

this.loadLocal(`${FileSystem.cacheDirectory + this.props.title}.${ extension }`);

});

Next up is getting the image data and updating the state. I wanted to set the image width to the device width, and then have the relative height since I couldn’t be sure of the dimensions these images were coming in with, which meant waiting on the Image.getSize function.

loadLocal(uri) { Image.getSize(uri, (width, height) => {

// once we have the original image dimensions, set the state to the relative ones

this.setState({ imguri: uri, loading: false, width: Dimensions.get('window').width, height: (height/width)*Dimensions.get('window').width });

}, (e) => {

// As always include an error fallback

console.log('getSize error:', e);

this.setState({ loading: false, failed: true })

})

}

Finally I just needed to update the render functions to reflect the states. I included a style prop so I could override the sizes, set the resizeMode etc. if needed.