In a React Native listing gallery, how do you show a placeholder while a remote Image loads and a fallback when it fails?
answer
- four load callbacks, one fires either way
- onLoad carries the source size
- onError carries an error string
- defaultSource is ignored in Android debug
- fallback must be bundled, not remote
basics
~20 sUse defaultSource for a bundled placeholder during loading, and onError to swap to a bundled fallback when the download fails. onLoadStart, onLoad and onLoadEnd track progress. On Android, defaultSource is ignored in debug builds, so test the placeholder in a release build.
solid answer
~40 s`Image` reports its lifecycle: `onLoadStart` when loading begins, `onLoad` on success with `nativeEvent.source` holding `width`, `height` and `uri`, `onError` on failure with `nativeEvent.error`, and `onLoadEnd` after either. For the placeholder, `defaultSource` takes a static image shown while `source` loads; on Android it is **ignored in debug builds**, so it only appears in release builds. For failures, keep a `failed` flag set in `onError` and render a bundled `require()` fallback, never another remote URL that can fail too. `loadingIndicatorSource` is an alternative placeholder, but on Android an `Image` with both it and `defaultSource` throws. Android also fades images in over `fadeDuration`, 300 ms by default.
code
tsx · 26 linesimport {useState} from 'react';
import {Image, StyleSheet} from 'react-native';
const PLACEHOLDER = require('./images/house-placeholder.png');
const FALLBACK = require('./images/photo-unavailable.png');
// Render as <ListingPhoto key={uri} uri={uri} /> so a new URL starts with a fresh failed flag.
export function ListingPhoto({uri}: {uri: string}) {
const [failed, setFailed] = useState(false);
return (
<Image
source={failed ? FALLBACK : {uri}}
defaultSource={PLACEHOLDER} // ignored in Android debug builds
onError={e => {
console.warn('listing photo failed', e.nativeEvent.error);
setFailed(true);
}}
style={styles.photo}
/>
);
}
const styles = StyleSheet.create({
photo: {width: '100%', aspectRatio: 4 / 3},
});go deeper
Recall onLoadStart, onLoad, onError and onLoadEnd, and that defaultSource shows a static image while loading.
Explain what each event carries, why onLoadEnd suits cleanup, and the Android defaultSource debug-build and loadingIndicatorSource rules.
Build thumbnails that keep a stable frame, fall back to bundled assets, reset state when the URL changes and never retry in a loop.
Define how the product represents missing media and how failures are surfaced to monitoring, so broken listings are fixed at the source.
## The load lifecycle A remote `Image` goes through a download and decode before it can show pixels. React Native exposes that as callbacks: | Callback | When | Payload | |---|---|---| | `onLoadStart` | loading begins | none | | `onProgress` (iOS) | as data arrives | progress event | | `onLoad` | loading succeeded | `nativeEvent.source` with `width`, `height`, `uri` | | `onError` | loading failed | `nativeEvent.error` | | `onLoadEnd` | after success **or** failure | none | `onLoadEnd` is the place to clear a spinner, because it runs whichever way the load ended. `onLoad`'s `source` size is the loaded image's size, useful for adjusting an aspect ratio after the fact; but laying out from it causes a jump, so prefer ratios known up front. ## Placeholders while loading Three options, from simplest to most flexible: 1. **`defaultSource`**: a static image displayed while `source` loads, typically a bundled `require()` of a neutral house silhouette. 2. **`loadingIndicatorSource`**: a resource shown as a loading indicator until the image is ready. 3. **Your own overlay**: a `View` or `ActivityIndicator` layered over the image area, hidden in `onLoadEnd`. Platform details that cause real bugs: - **On Android, `defaultSource` is ignored on debug builds.** A developer testing in debug sees no placeholder and assumes the prop is broken; it appears in release builds. - **On Android, `defaultSource` and `loadingIndicatorSource` cannot be combined**: the `Image` throws an error telling you to use one or the other. - **On Android, images fade in** over `fadeDuration`, 300 ms by default; set it to `0` for instant swaps such as a gallery that pages quickly. ## Fallbacks on failure Listing photos fail for ordinary reasons: a removed photo, an expired signed URL, no connectivity. A robust pattern: - keep a `failed` state per image; - set it in `onError`; - when `failed` is true, render a **bundled** fallback with `require()`, which cannot fail to download; - optionally show a small "photo unavailable" caption; - reset `failed` when the `uri` changes, for example by keying the component on the URL. Two mistakes to avoid: 1. **A remote fallback URL.** If it also fails, `onError` fires again and the component can bounce between sources. 2. **Retrying in a tight loop from `onError`.** Retry deliberately, with a user action or a back-off, not on every error event. ## Layout stays stable throughout Placeholders only work if the frame does not change size when the real image arrives. Give the `Image` its final dimensions or aspect ratio from the start, so the placeholder, the photo and the fallback all occupy the same box. Otherwise the gallery jumps each time a state changes. ## Testing the states Each state needs to be seen on purpose, because a fast connection in development hides most of them: 1. **Slow load**: throttle the network in the simulator or emulator settings and confirm the placeholder appears and the frame does not move. 2. **Failure**: point one listing at a URL that returns 404 and confirm the bundled fallback renders once, not repeatedly. 3. **Android release build**: confirm `defaultSource` is visible, since debug builds ignore it. 4. **URL change**: scroll or navigate so the same component receives a new URL after a failure, and confirm the new photo loads instead of the old fallback sticking. ## A checklist for a listing thumbnail - fixed frame from the design (`width` and `aspectRatio`); - `defaultSource={require('./placeholder.png')}`, verified in a release build on Android; - `onError` switching to a bundled fallback; - `onLoadEnd` clearing any overlay spinner; - `fadeDuration={0}` on Android if the fade feels slow while paging.
- In React Native, why does an Image's defaultSource never appear when you test on an Android debug build?The docs state that on Android the `defaultSource` prop is ignored on debug builds. The placeholder works in release builds, so verify it there, or use your own overlay placeholder if you need it visible during development.
- In React Native, why should an Image's error fallback be a require() asset rather than another URL?A bundled asset ships inside the app and cannot fail to download, so the fallback always renders. A second remote URL can fail for the same reasons as the first, such as no connectivity, which fires `onError` again and can leave the component switching between sources or showing nothing.
saying these in an interview costs you the question
- onLoadEnd fires only when the image loaded successfully.
- defaultSource shows on every Android build, including debug.
- An Image can use both defaultSource and loadingIndicatorSource on Android.
- onError should immediately retry the same URL until it succeeds.
- A placeholder removes the need to give the Image a fixed size.