When users tap quickly between images, your preview can flicker, hide the spinner at the wrong time, or even show the wrong photo. That’s a classic race condition during image loading. Here’s the core fix in one line—and why it works.
The problem
You show a full-screen preview of an image in a modal (or a floating box). A user taps Image A, it starts loading. Before it finishes, they tap Image B. If Image A completes late, its onLoad/onLoadEnd can still fire and mutate state that now “belongs” to Image B—hiding the spinner too early or causing flicker.
Why it happens
React Native’s Image kicks off a native request as soon as it renders with source={{ uri }}. If you keep the same Image instance mounted while swapping the URI, late callbacks from the previous load can still arrive and update your UI state.
The key solution: “latest selection wins” via force-remount
Force the preview Image to remount whenever the selection changes by adding a key tied to the selected image’s ID. When a new image is picked, the old preview unmounts—its native request is dropped and its callbacks won’t run—so only the current Image controls the spinner.
Fix
<Modal
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}
>
<View style={styles.modalContent}>
<Image
key={image?.id || "placeholder"} /// <-- FIX : latest selection wins
source={{ uri: image?.urls?.full || "https://placehold.co/600x400" }}
...
onLoadStart={() => setModalLoading(true)}
onLoadEnd={() => setModalLoading(false)}
/>
</View>
{modalLoading && (
<View style={styles.skeleton}>
<ActivityIndicator size="large" />
<Text style={styles.skeletonText}>Loading...</Text>
</View>
)}
</Modal>
Deep dive: what the Image actually does
Yes. React Native’s Image starts a native image request (Fresco/Glide on Android, SDWebImage/URLSession on iOS) when you render it with a source={{ uri }}. It’s not the JS fetch() API, so there’s no AbortController you can call from JS.
What you can rely on:
- Unmount/source change cancels natively: If you unmount the
Imageor change itskey/source, the native layer cancels/drops the old request and callbacks. That’s why usingkey={image.id}is effective against races. - No JS abort: You can’t abort a still-mounted
Imagerequest from JS; instead, remount or ignore late events (token guard). - Prefetch alternative:
Image.prefetch(url)can warm the cache, but it also can’t be aborted; you still need a “latest selection wins” guard.
If you need more control, libraries like expo-image/FastImage improve caching and cancel on unmount/source change as well, but they still don’t expose a JS abort handle for an in-place load.
Takeaway
For image previews where users can rapidly change selections, make the latest selection win by remounting the preview Image with key={selectedImage.id} and drive the loader off onLoadStart/onLoadEnd. This prevents late callbacks from earlier loads from ever touching your current UI state.
Top comments (0)