React Native Image Handling: Caching, Resizing and Picking With the 2026 Libraries

React Native image handling in 2026: expo-image caching and placeholders, fast-image 10, resizing and compressing uploads, pickers, and list bugs.

React Native Image Handling: Caching, Resizing and Picking With the 2026 Libraries

Open the profiler on almost any React Native app that scrolls a feed and the biggest line item is not JavaScript. It is image decoding. A 4,000 by 3,000 photo from a phone camera is 48 MB of pixels once decoded, your thumbnail is 120 by 120, and the stock Image component will happily decode the full thing, on the UI thread on older Androids, for every cell that scrolls into view. React Native image handling is three separate jobs: loading and caching, resizing and compressing, and picking from the camera roll. This guide covers all three with the libraries that are actually maintained in late 2026, and the defaults we use in production.

Versions were checked on npm on October 19, 2026: expo-image 58.0.15, react-native-fast-image 10.1.1, expo-image-manipulator 58.0.14, react-native-compressor 2.0.3, expo-image-picker 58.0.10, react-native-image-crop-picker 0.52.0, react-native-nitro-image 0.15.2, react-native-turbo-image 1.24.3. React Native 0.88.0 and Expo SDK 58 are current.

What the core Image component does and does not do

Core Image is fine for icons and local assets. It decodes on a background thread on iOS, caches by URL through the platform’s HTTP cache, and that is about it. The things it lacks: a memory cache you control, disk caching with a policy, progressive or blurhash placeholders, priority, prefetch that reports progress, animated WebP and AVIF on both platforms, and contentFit semantics that match the web. Android still decodes large bitmaps at full size unless you ask Fresco for a resize, and resizeMethod="resize" is the prop nobody sets.

Every library below exists to fix some subset of that list. For <Image> from core, three rules cover most of the pain:

<Image
  source={{ uri, width: 120, height: 120 }}   // tell Fresco the target size
  resizeMethod="resize"                        // Android: decode at target size
  style={{ width: 120, height: 120 }}
/>

Set explicit dimensions, set resizeMethod="resize" on Android, and never render remote images without a size. That alone fixes a surprising share of “scrolling is janky” reports.

Loading and caching: expo-image is the default now

expo-image is at about 3 million downloads a week, MIT, and works in bare React Native apps as well as Expo ones. It uses SDWebImage on iOS and Glide on Android, so you get the two best-maintained native image pipelines without writing any native code, plus a memory and disk cache, cachePolicy, placeholder with blurhash and thumbhash, transition, priority, recyclingKey for lists, animated GIF, WebP and AVIF, and web support through react-native-web.

npx expo install expo-image
import { Image } from 'expo-image';

const blurhash = 'LEHLk~WB2yk8pyo0adR*.7kCMdnj';

export function Avatar({ uri, size = 48 }: { uri: string; size?: number }) {
  return (
    <Image
      source={{ uri }}
      placeholder={{ blurhash }}
      contentFit="cover"
      transition={200}
      cachePolicy="memory-disk"
      recyclingKey={uri}
      style={{ width: size, height: size, borderRadius: size / 2 }}
    />
  );
}

Three settings carry most of the value. cachePolicy="memory-disk" is the right default for anything that is not a one-off; use "none" for signed URLs that expire and "disk" for large images you do not want to keep in RAM. recyclingKey tells a recycled list cell that the image changed, which is the fix for “wrong avatar flashes in the wrong row” in FlashList. And placeholder with a blurhash you compute at upload time turns the blank-then-pop into a fade, for 20 to 30 bytes per image. Prefetch with Image.prefetch(urls) before a screen that needs them, and clear caches with Image.clearDiskCache() in your debug menu, not in production code.

On SDK 58 the package also stopped reading UIScreen.main, part of the iOS 27 resizable window work, and 58.0.13 fixed blurry SVGs on Android 8.1 and older by drawing them in a software layer. SVG in expo-image is convenient for icons; for anything interactive use react-native-svg.

react-native-fast-image after 10.0

react-native-fast-image was the standard answer for years and then went quiet, which is why a fork exists as @d11/react-native-fast-image (8.13.0, ~70,000 downloads a week). This month the original woke up: 10.0.0 on October 12 moved the Android package to com.fastimage, removed the fallback prop and sources onProgress from the native views; 10.1.0 added source.diskCache to keep an image out of disk cache; 10.1.1 (October 18) fixed cacheOnly misses firing onLoad. It is at about 79,000 downloads a week on the same SDWebImage and Glide pair as expo-image. If you already have it and it works, the 10.x line is worth tracking. For a new project, expo-image does the same job with more features and a release every two weeks, so I would not start on FastImage today.

The Nitro options

react-native-nitro-image (0.15.2, ~126,000 downloads a week) is Marc Rousavy’s image library on Nitro Modules: a synchronous Image object you can load, resize, crop and convert to a JSI ArrayBuffer without a round trip through base64, plus a view component. It is the right tool when you process pixels (an avatar cropper, an ML preprocessing step, a camera pipeline from vision-camera) and overkill for a feed. react-native-turbo-image (1.24.3) is a smaller Nuke and Coil based viewer with blurhash and thumbhash support; solid, but in a different weight class from expo-image’s community.

Resizing and compressing before upload

The second job is making uploads small. Users pick a 12 MB HEIC and your API expects a 300 KB JPEG. Doing that on the server is possible but means you paid to move 12 MB over a mobile connection first.

expo-image-manipulator (58.0.14, ~2 million downloads a week) covers resize, crop, rotate, flip and format conversion with a chained API:

import { ImageManipulator, SaveFormat } from 'expo-image-manipulator';

export async function prepareUpload(uri: string) {
  const ctx = ImageManipulator.manipulate(uri);
  ctx.resize({ width: 1600 });          // height follows the aspect ratio
  const ref = await ctx.renderAsync();
  const out = await ref.saveAsync({ compress: 0.8, format: SaveFormat.JPEG });
  return out.uri;                        // local file, ready for FormData
}

For the EXIF and HEIC cases that trip up manual pipelines, react-native-compressor (2.0.3, July 2026, ~145,000 downloads a week, now on Nitro Modules) is the “do what WhatsApp does” library. Image.compress(uri) with compressionMethod: 'auto' picks a target size and quality that keeps photos readable at a fraction of the bytes, it handles video and audio too, and it reads orientation correctly so your sideways-photo bug disappears. @bam.tech/react-native-image-resizer (3.0.11) still works but has not been published since November 2024; treat it as maintenance mode.

Whichever you use, resize on the device to the largest size you will ever display (1600 px wide is plenty for a phone feed), strip EXIF unless you need location, and upload JPEG or WebP, not PNG, for photos. We cut upload volume by roughly four fifths in a messaging app by doing exactly that and nothing else.

Picking from the camera roll

expo-image-picker (58.0.10, ~3.3 million downloads a week) is the default for both Expo and bare apps. It returns the file URI, dimensions, optional base64 and EXIF, supports multiple selection, and uses the platform photo pickers that need no permission prompt on iOS 14+ and Android 13+:

import * as ImagePicker from 'expo-image-picker';

const result = await ImagePicker.launchImageLibraryAsync({
  mediaTypes: ['images'],
  allowsMultipleSelection: true,
  selectionLimit: 10,
  quality: 0.9,
  exif: false,
});
if (!result.canceled) {
  const uris = result.assets.map((a) => a.uri);
}

react-native-image-crop-picker (0.52.0, September 2026, ~126,000 downloads a week) is the one to reach for when you need a cropping UI with a fixed aspect ratio, circular crops for avatars, or compression at pick time with compressImageMaxWidth and compressImageQuality. It needs a development build on Expo. For an avatar flow the combination that gives the nicest result is crop-picker with cropperCircleOverlay plus expo-image-manipulator for the final resize.

Lists: the part that actually gets you fired

Everything above is wasted if the list does not cooperate. The checklist for an image-heavy FlashList or FlatList:

  • Fixed dimensions for every image so the list never re-measures after load.
  • recyclingKey (expo-image) so recycled cells drop the previous image immediately.
  • Request the size you render. If your API can return ?w=240 variants, use them; a CDN resize is cheaper than any on-device trick.
  • Prefetch the next page’s thumbnails when the user hits 70 percent of the current one.
  • Keep full-size images out of the list entirely. Open them in a viewer screen with its own cache policy.

For the viewer, react-native-image-viewing (0.2.2) is still installed in a lot of apps but was last published in 2022 and predates Reanimated 3 and 4; @likashefqet/react-native-image-zoom (4.3.0) is the Reanimated-and-Gesture-Handler based zoom component we would pick today, inside a modal or a native-stack screen.

Common bugs

  • Wrong image in a recycled cell. Set recyclingKey on expo-image; on core Image set a key on the cell that includes the URI.
  • Images rotate 90 degrees after upload. The JPEG carries an EXIF orientation your server ignores. Run the file through expo-image-manipulator or react-native-compressor, which bake the orientation into the pixels.
  • HEIC uploads fail on the backend. Convert on device to JPEG with saveAsync({ format: SaveFormat.JPEG }). iOS pickers return HEIC by default.
  • Out of memory on low-end Android. You are decoding full-size bitmaps. Send sizes with the source, use resizeMethod="resize", or switch to expo-image, which lets Glide downsample.
  • Signed URLs re-download on every open. The query string changes, so the cache key changes. Use cachePolicy="none" and cache the decoded file yourself by asset id, or strip the signature from the cache key on your CDN.
  • Blurry SVG on old Android. Fixed in expo-image 58.0.13 by drawing SVGs in a software layer.

Which libraries to use

Job Use Notes
Display remote images with caching and placeholders expo-image Works in bare apps; SDWebImage + Glide; blurhash, recyclingKey, web support
Existing FastImage codebase react-native-fast-image 10.x Active again since October 2026; migrate to expo-image when convenient
Pixel processing, ArrayBuffer access react-native-nitro-image Nitro Modules, synchronous API
Resize, crop, rotate, convert expo-image-manipulator Chained API, both platforms
Compress photos and video for upload react-native-compressor Auto mode, EXIF orientation handled
Pick from library or camera expo-image-picker Platform pickers, no permission prompt for the library
Crop with a fixed ratio or circle react-native-image-crop-picker Dev build on Expo
Pinch-to-zoom viewer @likashefqet/react-native-image-zoom Reanimated + Gesture Handler

My default stack in late 2026: expo-image for display, expo-image-picker plus expo-image-manipulator for the upload path, react-native-compressor when the app is media-heavy, all of it in a bare or Expo project alike. If you are weighing whether to be on Expo at all, this explainer covers that; the image libraries no longer force the decision either way.

FAQ

What is the best image library for React Native?

expo-image. It runs in bare React Native and Expo apps, wraps SDWebImage and Glide, and ships memory and disk caching, blurhash placeholders, priority and list recycling support.

Is react-native-fast-image still maintained?

It was quiet for a long stretch, which produced the @d11/react-native-fast-image fork, and then shipped 10.0.0, 10.1.0 and 10.1.1 in October 2026. It is usable, but expo-image has the larger community.

How do I cache images in React Native?

With expo-image, set cachePolicy="memory-disk"; images are cached by URL automatically. Prefetch with Image.prefetch(). For signed URLs, cache the file yourself by asset id.

How do I resize an image before uploading in React Native?

Use expo-image-manipulator: ImageManipulator.manipulate(uri).resize({ width: 1600 }), then renderAsync() and saveAsync({ compress: 0.8, format: SaveFormat.JPEG }). For photos and video together, react-native-compressor.

Why does my React Native app crash with out of memory when showing images?

Full-size bitmaps are being decoded for small views. Give every remote image explicit dimensions, set resizeMethod="resize" on Android, or use expo-image, which downsamples to the view size.

Leave a Reply 0

Your email address will not be published. Required fields are marked *