Fetching Responsive Images in React Native to Boost Performance

Jack Weatherilt
Finimize Engineering
5 min readAug 9, 2019

tldr; React Native’s Image component comes with a built-in handling for fetching images responsively. We can configure the React Native image component to fetch the best-sized image for our Image component without introducing any new dependencies.

At Finimize, the images really make our app shine. We have over 50 “packs” and release more every week. Packs are a series of guides that explore a variety of financial topics and investment strategies. Check the app out here. (And get £20 off our premium subscription by using this link!)

Each pack has its own cover, beautifully designed by our in-house illustrator. Each pack cover is displayed as a thumbnail in some places, and full size in others.

Images are used everywhere!

Clearly, we rely heavily on images — but loading them was a becoming a huge bottleneck in our React Native app’s performance. However, we made a giant leap in performance with just a few changes laid out here, and without any new dependencies or complicated logic.

You should fetch remote images responsively

The first thing to note here is that different phones have different pixel ratios. Let’s say we have an image of 64x64 size. This would require:

  • 128px by 128px on iPhone 7 (@2x pixel ratio with its Retina display)
  • 192px by 192px on iPhone Xs Max (@3x ratio)

That’s a 2.25x difference in the number of pixels — meaning more data over the wire and ultimately longer image load times and worse performance for your React Native app.

If you want some more detail on why this is the case, read on. If not, feel free to skip this section!

Why do different devices need different image sizes?

Although the screen sizes don’t differ that much physically, or in the measurement used in React Native code, the devices have a different number of pixels per “point”, the measurement used in React Native.

(If you’re coming from Photoshop, this is analogous to dots per inch — pixels per point simply measure the number of actual screen pixels that are used for a single “pixel”/point measurement on the device.)

Since we want our images to look crisp on each device, we could simply fetch the image that would display crisply on the largest device and hope that it scales down okay. (Spoiler: don’t do this.)

However, this means that for each image used on the iPhone 7, we’re fetching 125% more data than we need to because of its @2x pixel ratio compared to later iPhones’ @3x ratio. Using many different images, this can clearly end up taking a hit on an app’s performance.

The size difference between the image-versions needed for different devices is significant.

This is also a terrible idea if you use thumbnails anywhere. You don’t want to fetch the entire image if you’re only displaying it at 64px by 64px. Instead, we should just use a smaller version to save our users data and improve load times.

How React Native’s Image component helps

Normally when we use an Image component, we would pass it an object as its source. For example:

import React from "react"
import { Image } from "react-native"
const RandomCat = () => {
return (
<Image
style={{ height: 64, width: 64 }}
source={{ uri: https://cataas.com/cat }}
/>
)
}
export default RandomCat

This just tells React Native to go and fetch that image, regardless of the size of the Image component. If we’re only displaying a thumbnail, we’re wasting our users’ data as well as increasing our load times unnecessarily.

To solve these issues we can fetch images responsively. To do this we need a set of “breakpoints” for images: variations of an image at different sizes. When we need an image that is 64 actual device pixels in height and width, we simply fetch the image at the smallest breakpoint that is at least 64 pixels wide and tall.

The Image component can manage all this logic for us. If we pass it an array of source objects, with heights and widths specified, React Native will fetch the image which is best suited to display for the real pixel size of that image.

We don’t even need to specify the Image component’s size in its style since React Native calculates it on layout. All we need to do is pass an array of source objects (each with a URI, height, and width) and React Native will do the rest of the work for us.

import React from "react"
import { Image } from "react-native"
const sources = [
{
uri: "https://picsum.photos/id/30/64/64",
height: 64,
width: 64
},
{
uri: "https://picsum.photos/id/30/128/128",
height: 128,
width: 128
},
{
uri: "https://picsum.photos/id/30/256/256",
height: 256,
width: 256
},
{
uri: "https://picsum.photos/id/30/512/512",
height: 512,
width: 512
}
]
const SomeImage = () => {
// We don't even need to specify the height or width 🤯
return <Image source={sources} />
}

No dependencies or complicated logic needed!

The only pain-point here is that we have to generate a range of sizes for each image that we want to load responsively and then upload them to our web folder, S3, or some other service. Whilst it might be okay if you’re only using a few images in your app, it can quickly become a headache.

Bonus: Auto-generate image breakpoints with imgix

imgix takes the pain out of serving images at various breakpoints. It serves images stored in your web folder, S3, or Google Cloud Storage with the ability to edit it using nothing but query parameters.

All of your images are served from their global CDN, and costs just $3 per 1000 images served (ignoring variations in images).

Try: https://assets.imgix.net/unsplash/moon.jpg?fit=crop&w=500&h=400

The Moon displayed at 500px by 400px

Once we have an imgix URL, we can easily generate the URLs for our breakpoints in React Native if we want to.

import React from "react"
import { Image } from "react-native"
const breakpoints = [[100, 100], [200, 200], [300, 300]]
const sources = breakpoints.map([w, h] => ({
uri: `https://assets.imgix.net/unsplash/moon.jpg?fit=crop&w={w}&h={h}`,
height: w,
width: h
})
)
const TheMoon = () => {
return <Image source={sources} />
}
export default TheMoon

Alternatively, you can generate these on your server and send them to the app via an API request, like we do at Finimize.

imgix has far more options to edit your images on the fly too if you need them, as well as an option to serve and edit any image with a URL if you use their web proxy source.

New to Finimize? We’re on a mission to empower millennials to become their own financial advisors.

Find out more by subscribing to our financial email newsletter or downloading our app. By the way, we’re hiring!

(You can also get £20 off our premium subscription with this link!)

--

--