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)