@bhavesh2706/react-native-smart-image
v2.1.0
Published
Lightweight React Native image display with caching and opt-in UX flags — Expo and bare CLI. Powered by React Native Image. Zero runtime dependencies.
Maintainers
Readme
@bhavesh2706/react-native-smart-image
Focused image display for React Native (Expo & bare) on iOS and Android.
Demo screen recordings above live in media/ for GitHub only — they are not shipped in the npm tarball. The example app uses platform-specific GIFs under example/assets/demo.{ios,android,}.gif (Metro picks the right one).
- Load images from URL (also
require(),data:,file://,content://,ph://) - Cache via React Native
Image.prefetch+ in-memory known keys - Opt-in flags —
loadingIndicator,fullscreenOnPress,pinchZoom,compress(prop → providerconfig→ package default) - Themes — light / dark / system via
SmartImageProvider, token-driven chrome - Common formats through the OS / RN Image (JPEG, PNG; WebP/GIF/HEIC where the OS allows)
This package is not an editor, picker, uploader, or ML toolkit — it is display + cache + optional UX flags.
Lean deps: runtime dependencies stay empty. Required peers: react + react-native (>=0.72.0) only — no expo-image. Optional peers: @react-native-community/netinfo, expo-file-system. Missing optional modules degrade gracefully — they never throw.
npm · GitHub · Issues · YouTube Shorts
Why no expo-image (v2)
v2 uses React Native’s built-in Image so apps do not need an extra image npm package or install-expo-modules solely for this library.
| Kept | Weakened / lost vs expo-image |
|------|-------------------------------|
| Display URL / require / data / file | Disk cache path / size / clear disk APIs |
| Flags, themes, fullscreen, pinch | Blurhash/thumbhash native decode |
| Prefetch via Image.prefetch | Advanced cachePolicy tiers |
| JPEG/PNG (GIF/WebP OS-dependent) | HEIC / recycling / priority / downscale |
| Empty dependencies | Cross-dissolve transition / onProgress |
Unsupported props (recyclingKey, priority, allowDownscale, blurhash engine placeholder, etc.) are accepted and degrade gracefully.
Feature flags (dynamic + defaults)
Everything optional is a flag. Bare <SmartImage source={…} /> only displays + caches. Turn extras on when you need them — per image, or once via the provider.
| Flag | Package default | What it does |
|------|-----------------|--------------|
| loadingIndicator | false | ActivityIndicator while loading |
| fullscreenOnPress | false | Tap → full-screen lightbox |
| pinchZoom | true | Pinch / double-tap in fullscreen (only if fullscreen is on) |
| compress | off | URL rewrite before fetch (lighter remote bytes) |
Resolution order: prop → SmartImageProvider config → package default.
Install
Expo
npx expo install @bhavesh2706/react-native-smart-image
# optional
npx expo install @react-native-community/netinfo expo-file-systemOptional config plugin (always adds androidx.exifinterface so HEIC may
decode on some Android without crashing Fresco; photo library / storage
permissions stay opt-in):
{
"expo": {
"plugins": ["@bhavesh2706/react-native-smart-image"]
}
}Add { "photoLibrary": true } (or readExternalStorage) only if you load
privileged photo-library / content:// URIs:
{
"expo": {
"plugins": [["@bhavesh2706/react-native-smart-image", { "photoLibrary": true }]]
}
}| Surface | What works | | --- | --- | | Expo Go / web | Display, prefetch, flags, themes. | | Dev build / bare | Full set, including optional peers when installed. |
React Native CLI (bare)
npm install @bhavesh2706/react-native-smart-image
# no expo-image / install-expo-modules required for this package
cd ios && pod install && cd .. # only if your app already uses podsOptional peers are the same (netinfo, expo-file-system).
If you load HEIC / HEIF on Android, add this to android/app/build.gradle
(Fresco’s HeifExifUtil otherwise throws NoClassDefFoundError on an OkHttp
thread and kills the process):
implementation("androidx.exifinterface:exifinterface:1.3.7")The Expo config plugin injects the same line on prebuild.
Tested with: React 19.2.3 · React Native 0.87.1 (library root / bare CLI) · Expo SDK 57 / RN 0.86.3 (example). The example stays on SDK 57 until Expo ships a stable release with RN ≥0.87 (Expo 58 preview currently targets RN 0.88 RC, not 0.87.1). New Architecture is the primary target.
Quick start
import { SmartImage, SmartImageProvider } from '@bhavesh2706/react-native-smart-image';
export default function App() {
return (
<SmartImageProvider config={{ cachePolicy: 'memory-disk' }}>
<SmartImage
source="https://example.com/photo.jpg"
style={{ width: 200, height: 150 }}
contentFit="cover"
loadingIndicator // opt-in spinner
/>
</SmartImageProvider>
);
}Recipes
Per-image flags
<SmartImage source={url} style={styles.img} />
<SmartImage source={url} loadingIndicator />
<SmartImage source={url} compress={{ maxWidth: 400 }} />
<SmartImage source={url} fullscreenOnPress pinchZoom />App-wide defaults (still overridable per image)
<SmartImageProvider
config={{
loadingIndicator: true,
fullscreenOnPress: true,
cachePolicy: 'memory-disk',
}}
>
<App />
</SmartImageProvider>Caching
await SmartImage.prefetch(['https://…/a.jpg', 'https://…/b.jpg']);
await SmartImage.isCached(url); // session memory set (after prefetch/load)
await SmartImage.clearCache(); // clears known keys (no native disk clear)
await SmartImage.getCachePath(url); // always null with RN Image
await SmartImage.getCacheSize(); // typically null — RN Image has no disk cache APIPolicies (immutable · web · memory-disk · …) remain in the public API for compatibility; RN Image does not enforce native cache tiers.
Compress before display (opt-in, zero extra deps)
True on-device re-encode would need a native package — we don’t add one.
Instead compress lightens what is fetched:
- Host-aware URL rewrite (Picsum, Unsplash, Placehold.co, Cloudinary)
- Your CDN via
config.cdn.transform(orcreateQueryCdnTransform())
<SmartImage
source="https://picsum.photos/id/1015/900/600"
compress={{ quality: 0.6, maxWidth: 400 }}
onCompressed={(r) => console.log(r.strategy, r.uri)}
/>
// imperative (sync)
const { uri, compressed } = SmartImage.compress(url, { maxWidth: 800 });compress={true} → { quality: 0.7 }. Unknown hosts are left unchanged unless you set queryParams: true or configure a CDN.
Theme + dark mode
import {
SmartImageProvider,
useSmartColorScheme,
} from '@bhavesh2706/react-native-smart-image';
<SmartImageProvider
mode="system" // or 'light' | 'dark'
theme={{
light: { colors: { accent: '#4F46E5' } },
dark: { colors: { accent: '#818CF8' } },
}}
>
<App />
</SmartImageProvider>Props (SmartImage)
| Prop | Type | Default | Notes |
|------|------|---------|-------|
| source | string | { uri } | require() | … | — | Required. URL, headers object, data URI, local URI, … |
| loadingIndicator | boolean | false | Opt-in ActivityIndicator (or provider config) |
| fullscreenOnPress | boolean | false | Opt-in tap → lightbox (or provider config) |
| pinchZoom | boolean | true | Pinch / pan / double-tap when fullscreen is on |
| compress | boolean | options | off | Opt-in lighter fetch (URL rewrite) |
| onCompressed | (result) => void | — | Callback after compress rewrite |
| placeholder | 'skeleton' | 'shimmer' | image | — | Loading placeholder (blurhash → solid/overlay only) |
| cachePolicy | CachePolicy | provider / web | API-compatible; not enforced by RN Image |
| contentFit | ContentFit | provider / cover | Mapped to resizeMode |
| showRetry | boolean | — | Retry control on error |
| recyclingKey | string | — | Accepted; ignored by RN Image |
| priority | 'low' | 'normal' | 'high' | — | Decode queue only (not native fetch priority) |
| onPress / onFullscreenOpen / onFullscreenClose | callbacks | — | Press + lightbox lifecycle |
Formats (iOS & Android)
SmartImage displays whatever React Native Image + the OS can decode. JPEG / JPG / PNG are guaranteed on both iOS and Android.
| Format | iOS | Android | Notes |
|--------|-----|---------|--------|
| JPEG / JPG | ✅ | ✅ | Same codec; .jpg and .jpeg both work |
| PNG | ✅ | ✅ | Transparency when supported |
| HEIC / HEIF | ⚠️ | ⚠️ | May decode on some Android. Apps must ship androidx.exifinterface (Expo plugin and the example Gradle file add it) or Fresco crashes with NoClassDefFoundError: ExifInterface while reading orientation |
| WebP | ⚠️ | ✅ | OS / RN version dependent |
| GIF | ✅ | ✅ | Follows OS defaults (autoplay accepted, not controlled) |
| AVIF | ⚠️ | ⚠️ | OS / browser dependent |
| BMP | ✅ | ✅ | |
| ph:// / assets-library:// | ⚠️ | — | iOS Photos library URIs when OS allows |
| data:image/… | ✅ | ✅ | Base64 |
iOS upload helper
prepareImageForUpload resolves URI + MIME / fileName only (zero new deps). It does not re-encode HEIC → JPEG.
import { prepareImageForUpload, IOS_UPLOAD_FORMATS } from '@bhavesh2706/react-native-smart-image';
const prepared = await prepareImageForUpload(asset.uri);
formData.append('file', {
uri: prepared.uri,
type: prepared.mimeType,
name: prepared.fileName,
} as any);Large lists (thousands of images)
Use a virtualized list — do not put 1000+ images in a plain ScrollView.
<SmartImageProvider config={{ maxConcurrentDecodes: 6 }}>
<FlatList
data={items}
keyExtractor={(item) => item.id}
windowSize={5}
maxToRenderPerBatch={8}
removeClippedSubviews
renderItem={({ item }) => (
<SmartImage
source={item.url}
style={{ width: 120, height: 120 }}
recyclingKey={item.id}
priority="low"
/>
)}
/>
</SmartImageProvider>recyclingKey / priority still help SmartImage’s decode queue; they are not native FastImage-style controls.
Platform support
| Capability | iOS | Android | Expo Go | Web |
| --- | :---: | :---: | :---: | :---: |
| Display + prefetch + flags | ✅ | ✅ | ✅ | ✅ |
| Fullscreen + pinch zoom | ✅ | ✅ | ✅ | ✅ |
| HEIC / ph:// | ⚠️ | ⚠️ | ⚠️ | — |
| Offline / netinfo | ✅¹ | ✅¹ | ✅¹ | ✅¹ |
| Disk cache path / byte size | — | — | — | — |
¹ Optional @react-native-community/netinfo. Optional expo-file-system helps privileged URI helpers (ph:// / content://), not SmartImage disk bytes — RN Image has no disk cache API (getCachePath / getCacheSize stay null).
Tested with: React 19.2.3 · React Native 0.87.1 (library / bare CLI) · Expo SDK 57 / RN 0.86.3 (example — stays until stable Expo ships RN ≥0.87).
Other exports
SmartImageProvider · useSmartTheme / useSmartConfig / useSmartColorScheme · useSmartImage / useSmartImageCache / useNetworkStatus · createQueryCdnTransform · format helpers (guessImageFormat, isFormatSupported, describeSupportedFormats, CORE_IMAGE_FORMATS, IOS_UPLOAD_FORMATS) · prepareImageForUpload / isHeicUri · theme defaults (lightTheme, darkTheme, defaultThemes)
Example app
Kitchen-sink with 14 sections — see example/README.md:
cd example && npm install
npm run android # device or emulator
npm run ios # simulatorHighlights:
- Provider
configturns onloadingIndicator+fullscreenOnPressfor the demo - Section 2 plays a bundled local GIF via Metro platform resolution (
demo.ios.gif/demo.android.gif/demo.giffor web) - Section 12 cache toolkit: prefetch / isCached work; Clear resets in-memory keys (no disk byte size)
- Safe area via
react-native-safe-area-context
Hero GIFs in media/smart_Image_iOS.gif and media/smart_Image_android.gif are the matching platform recordings (GitHub docs only; not in the npm package).
Contributing
See CONTRIBUTING.md. Conventional Commits; CI runs typecheck, lint, format, tests, build, and size-limit. Keep the peer surface lean (react + react-native required; optional peers only when needed) and prefer opt-in flags with defaults for new UX.
License
MIT · Bhavesh Barot · npm · YouTube
