Version
react-native-nitro-image@0.15.2.
The gap
<NitroImage /> emits no events at all. The generated view config is the whole surface:
nitrogen/generated/shared/json/NitroImageViewConfig.json
{
"uiViewClassName": "NitroImageView",
"supportsRawText": false,
"bubblingEventTypes": {},
"directEventTypes": {},
"validAttributes": {
"image": true,
"resizeMode": true,
"recyclingKey": true,
"hybridRef": true
}
}
Four props, zero events, and NativeNitroImageViewMethods extends HybridViewMethods {} is empty
too. So there is no onLoad, no onError, no onDisplay.
Why it matters beyond "a missing callback"
The concrete thing it blocks is a cross-fade. expo-image has transition={220}, a
cross-dissolve when the source changes, and react-native-fast-image and react-native's own
Image both at least give you onLoad so you can build one. With <NitroImage /> there is no
transition prop and no event to drive one yourself, so a source change is always a hard cut.
That is most visible in exactly the case this library is good at. If you are swapping the source
on a view that is already showing something - a progressively refined image, a low-res preview
replaced by the full one, a thumbnail replaced by the original - the swap is the moment the user
sees, and right now it can only pop.
useImage() is not a substitute. It tells you when a frame is ready, but using it means holding
the decoded Image in JS and passing image={image}, and the spec is explicit that this opts
out of the recycling path:
Image: Shows a specific in-memory Image instance. Even when the view goes invisible, the
image will still be in-memory.
So the only way to observe a load today is to give up the memory behaviour that made you reach
for the ImageLoader variant in the first place. (See also #124, which is about the retention
side of that same trade.)
Secondary losses from having no events: no way to know a load failed so you can show your own
fallback, no way to measure time-to-first-paint per image, and no way to tell "still loading"
apart from "loaded, but the image is genuinely blank".
Suggested fix
Direct events on the view, matching what the ecosystem already expects:
export interface NativeNitroImageViewProps extends HybridViewProps {
// ...existing
onLoad?: (image: { width: number; height: number }) => void
onError?: (error: { message: string }) => void
}
onLoad fired after the bitmap is actually set on the native view (not when the promise
resolves, so it lines up with the frame the user sees) would be enough to build a fade in
Reanimated. onError fired from the catch that HybridImageView.onAppear already swallows
into a Log.e on Android would be a straight improvement over the current behaviour, which is
that a failed load is silent to the app.
A native transition prop would be nicer still for the common case, since the crossfade then
never crosses the bridge, but onLoad is the thing that unblocks people.
Context
Found while migrating Foodr off expo-image onto this
library (mrousavy/Foodr#19). The app streams generated dish photos - a preview frame lands in a
fraction of a second and is replaced by the finished image a few seconds later - and today
transition={220} is what makes that read as the picture sharpening rather than as the card
flickering. The migration PR is open and ships without the cross-fade, with a TODO at the call
site pointing here.
Version
react-native-nitro-image@0.15.2.The gap
<NitroImage />emits no events at all. The generated view config is the whole surface:nitrogen/generated/shared/json/NitroImageViewConfig.json{ "uiViewClassName": "NitroImageView", "supportsRawText": false, "bubblingEventTypes": {}, "directEventTypes": {}, "validAttributes": { "image": true, "resizeMode": true, "recyclingKey": true, "hybridRef": true } }Four props, zero events, and
NativeNitroImageViewMethods extends HybridViewMethods {}is emptytoo. So there is no
onLoad, noonError, noonDisplay.Why it matters beyond "a missing callback"
The concrete thing it blocks is a cross-fade.
expo-imagehastransition={220}, across-dissolve when the source changes, and
react-native-fast-imageandreact-native's ownImageboth at least give youonLoadso you can build one. With<NitroImage />there is notransition prop and no event to drive one yourself, so a source change is always a hard cut.
That is most visible in exactly the case this library is good at. If you are swapping the source
on a view that is already showing something - a progressively refined image, a low-res preview
replaced by the full one, a thumbnail replaced by the original - the swap is the moment the user
sees, and right now it can only pop.
useImage()is not a substitute. It tells you when a frame is ready, but using it means holdingthe decoded
Imagein JS and passingimage={image}, and the spec is explicit that this optsout of the recycling path:
So the only way to observe a load today is to give up the memory behaviour that made you reach
for the
ImageLoadervariant in the first place. (See also #124, which is about the retentionside of that same trade.)
Secondary losses from having no events: no way to know a load failed so you can show your own
fallback, no way to measure time-to-first-paint per image, and no way to tell "still loading"
apart from "loaded, but the image is genuinely blank".
Suggested fix
Direct events on the view, matching what the ecosystem already expects:
onLoadfired after the bitmap is actually set on the native view (not when the promiseresolves, so it lines up with the frame the user sees) would be enough to build a fade in
Reanimated.
onErrorfired from thecatchthatHybridImageView.onAppearalready swallowsinto a
Log.eon Android would be a straight improvement over the current behaviour, which isthat a failed load is silent to the app.
A native
transitionprop would be nicer still for the common case, since the crossfade thennever crosses the bridge, but
onLoadis the thing that unblocks people.Context
Found while migrating Foodr off
expo-imageonto thislibrary (mrousavy/Foodr#19). The app streams generated dish photos - a preview frame lands in a
fraction of a second and is replaced by the finished image a few seconds later - and today
transition={220}is what makes that read as the picture sharpening rather than as the cardflickering. The migration PR is open and ships without the cross-fade, with a
TODOat the callsite pointing here.