Files
react-native/packages/rn-tester/js/examples/Image/ImageExample.js
Mateo Guzmán d3d92b9a1d Unify RNTester Cache Policy Image example (#51580)
Summary:
The Image component in Android now supports the same cache control behaviour as in iOS, the examples needed to be separated in the past because the support was not the same for iOS and Android, but now that it is, we can unify the example.

## Changelog:

[INTERNAL] - Unify RNTester Cache Policy Image example

Pull Request resolved: https://github.com/facebook/react-native/pull/51580

Test Plan:
<details>
<summary>RNTester Screenshots</summary>

| iOS | Android |
|--------|-------|
| ![Simulator Screenshot - iPhone SE (3rd generation) - 2025-05-24 at 12 55 30](https://github.com/user-attachments/assets/f2fd680d-67e4-42db-bc91-f09fe5b0f947) | ![Screenshot_1748084132](https://github.com/user-attachments/assets/ef1b5454-4a85-48d7-be76-3107082b883e) |
</details>

Reviewed By: yungsters

Differential Revision: D75538812

Pulled By: Abbondanzo

fbshipit-source-id: 7705c8f824cb18cebc39e84b6f48979035dc104c
2025-05-31 13:13:39 -07:00

1740 lines
48 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {ImageProps, LayoutChangeEvent} from 'react-native';
import RNTesterButton from '../../components/RNTesterButton';
import RNTesterText from '../../components/RNTesterText';
import ImageCapInsetsExample from './ImageCapInsetsExample';
import React from 'react';
import {useEffect, useState} from 'react';
import {Image, ImageBackground, StyleSheet, Text, View} from 'react-native';
const IMAGE1 =
'https://www.facebook.com/assets/fb_lite_messaging/E2EE-settings@3x.png';
const IMAGE2 =
'https://www.facebook.com/ar_effect/external_textures/648609739826677.png';
const base64Icon =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
const IMAGE_PREFETCH_URL = `${IMAGE1}?r=1&t=${Date.now()}`;
const prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
type ImageSource = $ReadOnly<{
uri: string,
}>;
type BlobImageProps = $ReadOnly<{
url: string,
}>;
const BlobImage = ({url}: BlobImageProps): React.Node => {
const [objectURL, setObjectURL] = useState<?string>(null);
useEffect(() => {
// $FlowFixMe[unused-promise]
(async () => {
const result = await fetch(url);
const blob = await result.blob();
setObjectURL(URL.createObjectURL(blob));
})();
}, [url]);
return objectURL !== null ? (
<Image source={{uri: objectURL}} style={styles.base} />
) : (
<Text>Object URL not created yet</Text>
);
};
type BlobImageExampleState = {};
type BlobImageExampleProps = $ReadOnly<{
urls: string[],
}>;
class BlobImageExample extends React.Component<
BlobImageExampleProps,
BlobImageExampleState,
> {
render(): React.Node {
return (
<View style={styles.horizontal}>
{this.props.urls.map(url => (
<BlobImage key={url} url={url} />
))}
</View>
);
}
}
type NetworkImageCallbackExampleProps = $ReadOnly<{
source: ImageSource,
prefetchedSource: ImageSource,
}>;
const NetworkImageCallbackExample = ({
source,
prefetchedSource,
}: NetworkImageCallbackExampleProps): React.Node => {
const [events, setEvents] = useState<$ReadOnlyArray<string>>([]);
const [startLoadPrefetched, setStartLoadPrefetched] = useState(false);
const [mountTime, setMountTime] = useState(Date.now());
useEffect(() => {
setMountTime(Date.now());
}, []);
const _loadEventFired = (event: string) => {
setEvents(state => [...state, event]);
};
return (
<View>
<Image
source={source}
style={[styles.base, styles.visibleOverflow]}
onError={event => {
_loadEventFired(
`✘ onError "${event.nativeEvent.error}" (+${Date.now() - mountTime}ms)`,
);
}}
onLoadStart={() =>
_loadEventFired(`✔ onLoadStart (+${Date.now() - mountTime}ms)`)
}
onProgress={event => {
const {loaded, total} = event.nativeEvent;
const percent = Math.round((loaded / total) * 100);
_loadEventFired(
`✔ onProgress ${percent}% (+${Date.now() - mountTime}ms)`,
);
}}
onLoad={event => {
if (event.nativeEvent.source) {
const url = event.nativeEvent.source.uri;
_loadEventFired(
`✔ onLoad (+${Date.now() - mountTime}ms) for URL ${url}`,
);
} else {
_loadEventFired(`✔ onLoad (+${Date.now() - mountTime}ms)`);
}
}}
onLoadEnd={() => {
_loadEventFired(`✔ onLoadEnd (+${Date.now() - mountTime}ms)`);
setStartLoadPrefetched(true);
prefetchTask.then(
() => {
_loadEventFired(`✔ prefetch OK (+${Date.now() - mountTime}ms)`);
// $FlowFixMe[unused-promise]
Image.queryCache([IMAGE_PREFETCH_URL]).then(map => {
const result = map[IMAGE_PREFETCH_URL];
if (result) {
_loadEventFired(
`✔ queryCache "${result}" (+${Date.now() - mountTime}ms)`,
);
} else {
_loadEventFired(
`✘ queryCache (+${Date.now() - mountTime}ms)`,
);
}
});
},
error => {
_loadEventFired(
`✘ prefetch failed (+${Date.now() - mountTime}ms)`,
);
},
);
}}
/>
{startLoadPrefetched && (
<Image
source={prefetchedSource}
style={[styles.base, styles.visibleOverflow]}
onLoadStart={() =>
_loadEventFired(
`✔ (prefetched) onLoadStart (+${Date.now() - mountTime}ms)`,
)
}
onLoad={event => {
if (event.nativeEvent.source) {
const url = event.nativeEvent.source.uri;
_loadEventFired(
`✔ (prefetched) onLoad (+${
Date.now() - mountTime
}ms) for URL ${url}`,
);
} else {
_loadEventFired(
`✔ (prefetched) onLoad (+${Date.now() - mountTime}ms)`,
);
}
}}
onLoadEnd={() =>
_loadEventFired(
`✔ (prefetched) onLoadEnd (+${Date.now() - mountTime}ms)`,
)
}
/>
)}
<RNTesterText style={styles.networkImageText}>
{events.join('\n')}
</RNTesterText>
</View>
);
};
type NetworkImageExampleState = {
error: ?string,
loading: boolean,
progress: $ReadOnlyArray<number>,
};
class NetworkImageExample extends React.Component<
ImageProps,
NetworkImageExampleState,
> {
state: NetworkImageExampleState = {
error: null,
loading: false,
progress: [],
};
render(): React.Node {
return this.state.error != null ? (
<RNTesterText variant="label">{this.state.error}</RNTesterText>
) : (
<>
<Image
{...this.props}
style={[styles.base, styles.visibleOverflow]}
onLoadStart={e => this.setState({loading: true})}
onError={e =>
this.setState({error: e.nativeEvent.error, loading: false})
}
onProgress={e => {
const {loaded, total} = e.nativeEvent;
this.setState(prevState => ({
progress: [
...prevState.progress,
Math.round((100 * loaded) / total),
],
}));
}}
onLoad={() => this.setState({loading: false, error: null})}
/>
<RNTesterText variant="label">
Progress:{' '}
{this.state.progress.map(progress => `${progress}%`).join('\n')}
</RNTesterText>
</>
);
}
}
type ImageSizeExampleState = {
width: number,
height: number,
};
type ImageSizeExampleProps = $ReadOnly<{
source: ImageSource,
}>;
class ImageSizeExample extends React.Component<
ImageSizeExampleProps,
ImageSizeExampleState,
> {
state: ImageSizeExampleState = {
width: 0,
height: 0,
};
componentDidMount() {
Image.getSize(this.props.source.uri, (width, height) => {
this.setState({width, height});
});
}
render(): React.Node {
return (
<View style={styles.flexRow}>
<Image style={styles.imageSizeExample} source={this.props.source} />
<RNTesterText>
Actual dimensions:{'\n'}
Width: {this.state.width}, Height: {this.state.height}
</RNTesterText>
</View>
);
}
}
type MultipleSourcesExampleState = {
width: number,
height: number,
};
type MultipleSourcesExampleProps = $ReadOnly<{}>;
class MultipleSourcesExample extends React.Component<
MultipleSourcesExampleProps,
MultipleSourcesExampleState,
> {
state: MultipleSourcesExampleState = {
width: 30,
height: 30,
};
increaseImageSize = () => {
if (this.state.width >= 100) {
return;
}
this.setState({
width: this.state.width + 10,
height: this.state.height + 10,
});
};
decreaseImageSize = () => {
if (this.state.width <= 10) {
return;
}
this.setState({
width: this.state.width - 10,
height: this.state.height - 10,
});
};
render(): React.Node {
return (
<View>
<View style={styles.spaceBetweenView}>
<RNTesterText
style={styles.touchableText}
onPress={this.decreaseImageSize}>
Decrease image size
</RNTesterText>
<RNTesterText
style={styles.touchableText}
onPress={this.increaseImageSize}>
Increase image size
</RNTesterText>
</View>
<RNTesterText>
Container image size: {this.state.width}x{this.state.height}{' '}
</RNTesterText>
<View style={{height: this.state.height, width: this.state.width}}>
<Image
style={styles.flex}
source={[
{
uri: IMAGE1,
width: 38,
height: 38,
},
{
uri: IMAGE2,
width: 100,
height: 100,
},
]}
/>
</View>
</View>
);
}
}
type LoadingIndicatorSourceExampleState = {
imageHash: number,
};
type LoadingIndicatorSourceExampleProps = $ReadOnly<{}>;
class LoadingIndicatorSourceExample extends React.Component<
LoadingIndicatorSourceExampleProps,
LoadingIndicatorSourceExampleState,
> {
state: LoadingIndicatorSourceExampleState = {
imageHash: Date.now(),
};
reloadImage = () => {
this.setState({
imageHash: Date.now(),
});
};
loaderGif: {uri: string} = {
uri: 'https://media1.giphy.com/media/3oEjI6SIIHBdRxXI40/200.gif',
};
render(): React.Node {
const loadingImage = {
uri: `${IMAGE2}?hash=${this.state.imageHash}`,
};
return (
<View>
<View style={styles.spaceBetweenView}>
<RNTesterText style={styles.touchableText} onPress={this.reloadImage}>
Refresh Image
</RNTesterText>
</View>
<Image
loadingIndicatorSource={this.loaderGif}
source={loadingImage}
style={styles.base}
/>
<RNTesterText>Image Hash: {this.state.imageHash}</RNTesterText>
<RNTesterText>Image URI: {loadingImage.uri}</RNTesterText>
</View>
);
}
}
type FadeDurationExampleState = {
imageHash: number,
};
type FadeDurationExampleProps = $ReadOnly<{}>;
class FadeDurationExample extends React.Component<
FadeDurationExampleProps,
FadeDurationExampleState,
> {
state: FadeDurationExampleState = {
imageHash: Date.now(),
};
reloadImage = () => {
this.setState({
imageHash: Date.now(),
});
};
render(): React.Node {
const loadingImage = {
uri: `${IMAGE2}?hash=${this.state.imageHash}`,
};
return (
<View>
<View style={styles.spaceBetweenView}>
<Text style={styles.touchableText} onPress={this.reloadImage}>
Refresh Image
</Text>
</View>
<Image fadeDuration={1500} source={loadingImage} style={styles.base} />
<RNTesterText>
This image will fade in over the time of 1.5s.
</RNTesterText>
</View>
);
}
}
type OnLayoutExampleState = {
width: number,
height: number,
layoutHandlerMessage: string,
};
type OnLayoutExampleProps = $ReadOnly<{}>;
class OnLayoutExample extends React.Component<
OnLayoutExampleProps,
OnLayoutExampleState,
> {
state: OnLayoutExampleState = {
width: 30,
height: 30,
layoutHandlerMessage: 'No Message',
};
onLayoutHandler = (event: LayoutChangeEvent) => {
this.setState({
width: this.state.width,
height: this.state.height,
layoutHandlerMessage: JSON.stringify(event.nativeEvent),
});
console.log(event.nativeEvent);
};
increaseImageSize = () => {
if (this.state.width >= 100) {
return;
}
this.setState({
width: this.state.width + 10,
height: this.state.height + 10,
});
};
decreaseImageSize = () => {
if (this.state.width <= 10) {
return;
}
this.setState({
width: this.state.width - 10,
height: this.state.height - 10,
});
};
render(): React.Node {
return (
<View>
<RNTesterText>
Adjust the image size to trigger the OnLayout handler.
</RNTesterText>
<View style={styles.spaceBetweenView}>
<Text style={styles.touchableText} onPress={this.decreaseImageSize}>
Decrease image size
</Text>
<Text style={styles.touchableText} onPress={this.increaseImageSize}>
Increase image size
</Text>
</View>
<RNTesterText>
Container image size: {this.state.width}x{this.state.height}{' '}
</RNTesterText>
<View style={{height: this.state.height, width: this.state.width}}>
<Image
onLayout={this.onLayoutHandler}
style={styles.flex}
source={[
{
uri: IMAGE1,
width: 38,
height: 38,
},
{
uri: IMAGE1,
width: 76,
height: 76,
},
{
uri: IMAGE2,
width: 400,
height: 400,
},
]}
/>
</View>
<RNTesterText>
Layout Handler Message: {this.state.layoutHandlerMessage}
</RNTesterText>
</View>
);
}
}
type OnPartialLoadExampleState = {
hasLoaded: boolean,
};
type OnPartialLoadExampleProps = $ReadOnly<{}>;
class OnPartialLoadExample extends React.Component<
OnPartialLoadExampleProps,
OnPartialLoadExampleState,
> {
state: OnPartialLoadExampleState = {
hasLoaded: false,
};
partialLoadHandler = () => {
this.setState({
hasLoaded: true,
});
};
render(): React.Node {
return (
<View>
<RNTesterText>
Partial Load Function Executed: {JSON.stringify(this.state.hasLoaded)}
</RNTesterText>
<Image
source={{
uri: `https://images.pexels.com/photos/671557/pexels-photo-671557.jpeg?&buster=${Math.random()}`,
}}
onPartialLoad={this.partialLoadHandler}
style={styles.base}
/>
</View>
);
}
}
const VectorDrawableExample = () => {
return (
<View style={styles.flex} testID="vector-drawable-example">
<View style={styles.horizontal}>
<Image
source={require('../../assets/ic_android.xml')}
style={styles.vectorDrawable}
/>
<Image
source={require('../../assets/ic_android.xml')}
style={styles.vectorDrawable}
tintColor="red"
/>
</View>
</View>
);
};
function CacheControlExample(): React.Node {
const [reload, setReload] = useState(0);
const onReload = () => {
setReload(prevReload => prevReload + 1);
};
return (
<>
<View style={styles.horizontal}>
<View>
<RNTesterText style={styles.resizeModeText}>Default</RNTesterText>
<Image
source={{
uri: fullImage.uri + '?cacheBust=default',
cache: 'default',
}}
style={styles.base}
key={reload}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>Reload</RNTesterText>
<Image
source={{
uri: fullImage.uri + '?cacheBust=reload',
cache: 'reload',
}}
style={styles.base}
key={reload}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>Force-cache</RNTesterText>
<Image
source={{
uri: fullImage.uri + '?cacheBust=force-cache',
cache: 'force-cache',
}}
style={styles.base}
key={reload}
onError={e => console.log(e.nativeEvent.error)}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>
Only-if-cached
</RNTesterText>
<Image
source={{
uri: fullImage.uri + '?cacheBust=only-if-cached',
cache: 'only-if-cached',
}}
style={styles.base}
key={reload}
onError={e => console.log(e.nativeEvent.error)}
/>
</View>
</View>
<View style={styles.horizontal}>
<View style={styles.cachePolicyAndroidButtonContainer}>
<RNTesterButton onPress={onReload}>
Re-render image components
</RNTesterButton>
</View>
</View>
</>
);
}
const fullImage: ImageSource = {
uri: IMAGE2,
};
const smallImage = {
uri: IMAGE1,
};
const styles = StyleSheet.create({
base: {
width: 64,
height: 64,
margin: 4,
},
visibleOverflow: {
overflow: 'visible',
},
leftMargin: {
marginLeft: 10,
},
background: {
backgroundColor: '#222222',
},
sectionText: {
marginVertical: 6,
},
nestedText: {
marginLeft: 12,
marginTop: 20,
backgroundColor: 'transparent',
color: 'white',
},
resizeMode: {
width: 90,
height: 60,
borderWidth: 0.5,
borderColor: 'black',
},
resizeModeText: {
fontSize: 11,
marginBottom: 3,
},
icon: {
width: 15,
height: 15,
margin: 4,
},
horizontal: {
flexDirection: 'row',
flexWrap: 'wrap',
},
gif: {
flex: 1,
height: 200,
},
base64: {
flex: 1,
height: 50,
resizeMode: 'contain',
},
touchableText: {
fontWeight: '500',
color: 'blue',
},
networkImageText: {
marginTop: 20,
},
flex: {
flex: 1,
},
imageWithBorderRadius: {
borderRadius: 5,
},
imageSizeExample: {
width: 60,
height: 60,
backgroundColor: 'transparent',
marginRight: 10,
},
flexRow: {
flexDirection: 'row',
},
spaceBetweenView: {
flexDirection: 'row',
justifyContent: 'space-between',
},
customBorderColor: {
borderWidth: 5,
borderColor: '#f099f0',
},
borderTopLeftRadius: {
borderTopLeftRadius: 20,
},
opacity1: {
opacity: 1,
},
opacity2: {
opacity: 0.8,
},
opacity3: {
opacity: 0.6,
},
opacity4: {
opacity: 0.4,
},
opacity5: {
opacity: 0.2,
},
opacity6: {
opacity: 0,
},
transparentImageBackground: {
width: 60,
height: 60,
backgroundColor: 'transparent',
},
tintColor1: {
tintColor: '#ff2d55',
},
tintColor2: {
tintColor: '#5ac8fa',
},
tintColor3: {
tintColor: '#4cd964',
},
tintColor4: {
tintColor: '#8e8e93',
},
objectFitContain: {
objectFit: 'contain',
},
objectFitCover: {
objectFit: 'cover',
},
objectFitFill: {
objectFit: 'fill',
},
objectFitScaleDown: {
objectFit: 'scale-down',
},
objectFitNone: {
objectFit: 'none',
},
imageInBundle: {
borderColor: 'yellow',
borderWidth: 4,
},
imageInAssetCatalog: {
marginLeft: 10,
borderColor: 'blue',
borderWidth: 4,
},
backgroundColor1: {
backgroundColor: 'rgba(0, 0, 100, 0.25)',
},
backgroundColor2: {
backgroundColor: 'red',
},
backgroundColor3: {
backgroundColor: 'red',
borderColor: 'green',
borderWidth: 3,
borderRadius: 25,
},
borderRadius1: {
borderRadius: 19,
},
borderRadius2: {
borderWidth: 4,
borderTopLeftRadius: 10,
borderBottomRightRadius: 20,
borderColor: 'green',
},
borderRadius3: {
resizeMode: 'cover',
width: 90,
borderWidth: 4,
borderTopLeftRadius: 10,
borderTopRightRadius: 20,
borderBottomRightRadius: 30,
borderBottomLeftRadius: 40,
borderColor: 'red',
},
borderRadius4: {
resizeMode: 'stretch',
width: 90,
borderWidth: 4,
borderTopLeftRadius: 10,
borderTopRightRadius: 20,
borderBottomRightRadius: 30,
borderBottomLeftRadius: 40,
borderColor: 'red',
backgroundColor: 'yellow',
},
borderRadius5: {
resizeMode: 'contain',
width: 90,
borderWidth: 4,
borderTopLeftRadius: 10,
borderTopRightRadius: 20,
borderBottomRightRadius: 30,
borderBottomLeftRadius: 40,
borderColor: 'red',
backgroundColor: 'yellow',
},
boxShadow: {
margin: 10,
},
boxShadowWithBackground: {
backgroundColor: 'lightblue',
boxShadow: '0px 0px 10px 0px rgba(0, 0, 0, 0.5)',
},
boxShadowMultiOutsetInset: {
boxShadow:
'-5px -5px 10px 2px rgba(0, 128, 0, 0.5), 5px 5px 10px 2px rgba(128, 0, 0, 0.5), inset orange 0px 0px 20px 0px, black 0px 0px 5px 1px',
borderColor: 'blue',
borderWidth: 1,
borderRadius: 20,
},
boxShadowAsymetricallyRounded: {
borderTopLeftRadius: 0,
borderTopRightRadius: 5,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 20,
marginRight: 80,
marginTop: 40,
boxShadow: '80px 0px 10px 0px hotpink',
transform: 'rotate(-15deg)',
},
vectorDrawable: {
height: 64,
width: 64,
},
resizedImage: {
height: 100,
width: '500%',
},
cachePolicyAndroidButtonContainer: {
flex: 1,
alignItems: 'center',
marginTop: 10,
},
});
exports.displayName = (undefined: ?string);
exports.framework = 'React';
exports.title = 'Image';
exports.category = 'Basic';
exports.description =
'Base component for displaying different types of images.';
exports.examples = [
{
title: 'Plain Network Image with `source` prop.',
description: ('If the `source` prop `uri` property is prefixed with ' +
'"http", then it will be downloaded from the network.': string),
render: function (): React.Node {
return <Image source={fullImage} style={styles.base} />;
},
},
{
title: 'Plain Network Image with `src` prop.',
description: ('If the `src` prop is defined with ' +
'"http", then it will be downloaded from the network.': string),
render: function (): React.Node {
return <Image src={fullImage.uri} style={styles.base} />;
},
},
{
title: 'Multiple Image Source using the `srcSet` prop.',
description:
('A list of comma seperated uris along with scale are provided in `srcSet`.' +
'An appropriate value will be used based on the scale of the device.': string),
render: function (): React.Node {
return (
<Image
width={64}
height={64}
srcSet={`${IMAGE2} 4x, ${IMAGE1} 2x`}
style={styles.base}
/>
);
},
},
{
title: 'Plain Blob Image',
description: ('If the `source` prop `uri` property is an object URL, ' +
'then it will be resolved using `BlobProvider` (Android) or `RCTBlobManager` (iOS).': string),
render: function (): React.Node {
return <BlobImageExample urls={[IMAGE1, IMAGE2]} />;
},
},
{
title: 'Plain Static Image',
description:
('Static assets should be placed in the source code tree, and ' +
'required in the same way as JavaScript modules.': string),
render: function (): React.Node {
return (
<View style={styles.horizontal}>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={styles.icon}
/>
<Image
source={require('../../assets/uie_thumb_selected.png')}
style={styles.icon}
/>
<Image
source={require('../../assets/uie_comment_normal.png')}
style={styles.icon}
/>
<Image
source={require('../../assets/uie_comment_highlighted.png')}
style={styles.icon}
/>
</View>
);
},
},
{
title: 'Image Loading Events',
render: function (): React.Node {
return (
<NetworkImageCallbackExample
source={{
uri: `${IMAGE1}?r=1&t=${Date.now()}`,
}}
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}
/>
);
},
},
{
title: 'Error Handler',
render: function (): React.Node {
return (
<NetworkImageExample
source={{
uri: IMAGE1 + '_TYPO',
}}
/>
);
},
},
{
title: 'Error Handler for Large Images',
render: function (): React.Node {
return (
<NetworkImageExample
resizeMethod="none"
// 6000x5340 ~ 128 MB
source={require('../../assets/very-large-image.png')}
/>
);
},
},
{
title: 'Image Download Progress',
render: function (): React.Node {
return (
<NetworkImageExample
source={{
uri: `${IMAGE1}?r=1`,
}}
/>
);
},
},
{
title: 'defaultSource',
description: 'Show a placeholder image when a network image is loading',
render: function (): React.Node {
return (
<Image
defaultSource={require('../../assets/bunny.png')}
source={{
// Note: Use a large image and bust cache so we can in fact
// visualize the `defaultSource` image.
uri: fullImage.uri + '?cacheBust=notinCache' + Date.now(),
}}
style={styles.base}
/>
);
},
platform: 'ios',
},
{
title: 'Cache Policy',
description: `- First image will be loaded and cached.
- Second image is the same but will be reloaded if re-rendered as the cache policy is set to reload.
- Third image will try to load from the cache first and only use the network if the cached version is unavailable.
- Fourth image will never be loaded as the cache policy is set to only-if-cached and the image has not been loaded before.`,
render: function (): React.Node {
return <CacheControlExample />;
},
},
{
title: 'Borders',
name: 'borders',
render: function (): React.Node {
return (
<View style={styles.horizontal} testID="borders-example">
<Image
source={smallImage}
style={[styles.base, styles.background, styles.customBorderColor]}
/>
</View>
);
},
},
{
title: 'Border Radius',
name: 'border-radius',
render: function (): React.Node {
return (
<View style={styles.horizontal} testID="border-radius-example">
<Image
style={[styles.base, styles.imageWithBorderRadius]}
source={fullImage}
/>
<Image
style={[styles.base, styles.borderRadius1]}
source={fullImage}
/>
<Image
style={[styles.base, styles.borderTopLeftRadius]}
source={fullImage}
/>
<Image
style={[styles.base, styles.borderRadius2]}
source={fullImage}
/>
<Image
style={[styles.base, styles.borderRadius3]}
source={fullImage}
/>
<Image
style={[styles.base, styles.borderRadius4]}
source={fullImage}
/>
<Image
style={[styles.base, styles.borderRadius5]}
source={fullImage}
/>
</View>
);
},
},
{
title: 'Background Color',
name: 'background-color',
render: function (): React.Node {
return (
<View style={styles.horizontal} testID="background-color-example">
<Image source={smallImage} style={styles.base} />
<Image
style={[styles.base, styles.backgroundColor1]}
source={smallImage}
/>
<Image
style={[styles.base, styles.backgroundColor2]}
source={smallImage}
/>
<Image
style={[styles.base, styles.backgroundColor3]}
source={smallImage}
/>
</View>
);
},
},
{
title: 'Box Shadow',
name: 'box-shadow',
render: function (): React.Node {
return (
<View style={styles.horizontal} testID="box-shadow-example">
<Image
style={[
styles.base,
styles.boxShadow,
styles.boxShadowWithBackground,
]}
source={smallImage}
/>
<Image
style={[
styles.base,
styles.boxShadow,
styles.boxShadowMultiOutsetInset,
]}
source={smallImage}
/>
<Image
style={[
styles.base,
styles.boxShadow,
styles.boxShadowAsymetricallyRounded,
]}
source={fullImage}
/>
</View>
);
},
},
{
title: 'Opacity',
render: function (): React.Node {
return (
<View style={styles.horizontal}>
<Image style={[styles.base, styles.opacity1]} source={fullImage} />
<Image style={[styles.base, styles.opacity2]} source={fullImage} />
<Image style={[styles.base, styles.opacity3]} source={fullImage} />
<Image style={[styles.base, styles.opacity4]} source={fullImage} />
<Image style={[styles.base, styles.opacity5]} source={fullImage} />
<Image style={[styles.base, styles.opacity6]} source={fullImage} />
</View>
);
},
},
{
title: 'Nesting content inside <Image> component',
render: function (): React.Node {
return (
<View style={styles.base}>
<Image
style={{...StyleSheet.absoluteFillObject}}
source={fullImage}
/>
<Text style={styles.nestedText}>React</Text>
</View>
);
},
},
{
title: 'Nesting content inside <ImageBackground> component',
render: function (): React.Node {
return (
<ImageBackground
style={styles.transparentImageBackground}
source={fullImage}>
<Text style={styles.nestedText}>React</Text>
</ImageBackground>
);
},
},
{
title: 'Tint Color',
description: ('The `tintColor` prop changes all the non-alpha ' +
'pixels to the tint color.': string),
render: function (): React.Node {
return (
<View>
<View style={styles.horizontal}>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor1,
]}
tintColor={'#5ac8fa'}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[styles.icon, styles.imageWithBorderRadius]}
tintColor={'#4cd964'}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[styles.icon, styles.imageWithBorderRadius]}
tintColor={'#ff2d55'}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[styles.icon, styles.imageWithBorderRadius]}
tintColor={'#8e8e93'}
/>
</View>
<RNTesterText style={styles.sectionText} variant="label">
It also works using the `tintColor` style prop
</RNTesterText>
<View style={styles.horizontal}>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor2,
]}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor3,
]}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor1,
]}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor4,
]}
/>
</View>
<RNTesterText style={styles.sectionText} variant="label">
The `tintColor` prop has precedence over the `tintColor` style prop
</RNTesterText>
<View style={styles.horizontal}>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor2,
]}
tintColor={'#5ac8fa'}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor3,
]}
tintColor={'#5ac8fa'}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor1,
]}
tintColor={'#5ac8fa'}
/>
<Image
source={require('../../assets/uie_thumb_normal.png')}
style={[
styles.icon,
styles.imageWithBorderRadius,
styles.tintColor4,
]}
tintColor={'#5ac8fa'}
/>
</View>
<RNTesterText style={styles.sectionText} variant="label">
It also works with downloaded images:
</RNTesterText>
<View style={styles.horizontal}>
<Image
source={smallImage}
style={[
styles.base,
styles.imageWithBorderRadius,
styles.tintColor2,
]}
/>
<Image
source={smallImage}
style={[
styles.base,
styles.imageWithBorderRadius,
styles.tintColor3,
]}
/>
<Image
source={smallImage}
style={[
styles.base,
styles.imageWithBorderRadius,
styles.tintColor1,
]}
/>
<Image
source={smallImage}
style={[
styles.base,
styles.imageWithBorderRadius,
styles.tintColor4,
]}
/>
</View>
</View>
);
},
},
{
title: 'Object Fit',
description: ('The `objectFit` style prop controls how the image is ' +
'rendered within the frame.': string),
render: function (): React.Node {
return (
<View>
{[smallImage, fullImage].map((image, index) => {
return (
<View key={index}>
<View style={styles.horizontal}>
<View>
<RNTesterText style={styles.resizeModeText}>
Contain
</RNTesterText>
<Image
style={[styles.resizeMode, styles.objectFitContain]}
source={image}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>
Cover
</RNTesterText>
<Image
style={[styles.resizeMode, styles.objectFitCover]}
source={image}
/>
</View>
</View>
<View style={styles.horizontal}>
<View>
<RNTesterText style={styles.resizeModeText}>
Fill
</RNTesterText>
<Image
style={[styles.resizeMode, styles.objectFitFill]}
source={image}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>
Scale Down
</RNTesterText>
<Image
style={[styles.resizeMode, styles.objectFitScaleDown]}
source={image}
/>
</View>
</View>
<View style={styles.horizontal}>
<View>
<RNTesterText style={styles.resizeModeText}>
None
</RNTesterText>
<Image
style={[styles.resizeMode, styles.objectFitNone]}
source={image}
/>
</View>
</View>
</View>
);
})}
</View>
);
},
},
{
title: 'Resize Mode',
description: ('The `resizeMode` style prop controls how the image is ' +
'rendered within the frame.': string),
render: function (): React.Node {
return (
<View>
{[smallImage, fullImage].map((image, index) => {
return (
<View key={index}>
<View style={styles.horizontal}>
<View>
<RNTesterText style={styles.resizeModeText}>
Contain
</RNTesterText>
<Image
style={styles.resizeMode}
resizeMode="contain"
source={image}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>
Cover
</RNTesterText>
<Image
style={styles.resizeMode}
resizeMode="cover"
source={image}
/>
</View>
</View>
<View style={styles.horizontal}>
<View>
<RNTesterText style={styles.resizeModeText}>
Stretch
</RNTesterText>
<Image
style={styles.resizeMode}
resizeMode="stretch"
source={image}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>
Repeat
</RNTesterText>
<Image
style={styles.resizeMode}
resizeMode="repeat"
source={image}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>
Center
</RNTesterText>
<Image
style={styles.resizeMode}
resizeMode="center"
source={image}
/>
</View>
</View>
<View style={styles.horizontal}>
<View>
<RNTesterText style={styles.resizeModeText}>
None
</RNTesterText>
<Image
style={styles.resizeMode}
resizeMode="none"
source={image}
/>
</View>
</View>
</View>
);
})}
</View>
);
},
},
{
title: 'Animated GIF',
render: function (): React.Node {
return (
<Image
style={styles.gif}
source={require('../../assets/tumblr_mfqekpMktw1rn90umo1_500.gif')}
/>
);
},
platform: 'ios',
},
{
title: 'Base64 image',
render: function (): React.Node {
return (
<Image style={styles.base64} source={{uri: base64Icon, scale: 3}} />
);
},
platform: 'ios',
},
{
title: 'Cap Insets',
description:
('When the image is resized, the corners of the size specified ' +
'by capInsets will stay a fixed size, but the center content and ' +
'borders of the image will be stretched. This is useful for creating ' +
'resizable rounded buttons, shadows, and other resizable assets.': string),
render: function (): React.Node {
return <ImageCapInsetsExample />;
},
platform: 'ios',
},
{
title: 'Image Size',
render: function (): React.Node {
return <ImageSizeExample source={fullImage} />;
},
},
{
title: 'MultipleSourcesExample',
description:
('The `source` prop allows passing in an array of uris, so that native to choose which image ' +
'to diplay based on the size of the of the target image': string),
render: function (): React.Node {
return <MultipleSourcesExample />;
},
},
{
title: 'Legacy local image',
description: ('Images shipped with the native bundle, but not managed ' +
'by the JS packager': string),
render: function (): React.Node {
return <Image source={{uri: 'legacy_image', width: 120, height: 120}} />;
},
},
{
title: 'Bundled images',
description: 'Images shipped in a separate native bundle',
render: function (): React.Node {
return (
<View style={styles.flexRow}>
<Image
source={{
uri: 'ImageInBundle',
bundle: 'RNTesterBundle',
width: 100,
height: 100,
}}
style={styles.imageInBundle}
/>
<Image
source={{
uri: 'ImageInAssetCatalog',
bundle: 'RNTesterBundle',
width: 100,
height: 100,
}}
style={styles.imageInAssetCatalog}
/>
</View>
);
},
platform: 'ios',
},
{
title: 'Blur Radius',
render: function (): React.Node {
return (
<View style={styles.horizontal}>
<Image style={styles.base} source={fullImage} blurRadius={0} />
<Image style={styles.base} source={fullImage} blurRadius={5} />
<Image style={styles.base} source={fullImage} blurRadius={10} />
<Image style={styles.base} source={fullImage} blurRadius={15} />
<Image style={styles.base} source={fullImage} blurRadius={20} />
<Image style={styles.base} source={fullImage} blurRadius={25} />
</View>
);
},
},
{
title: 'Accessibility',
description:
('If the `accessible` (boolean) prop is set to True, the image will be indicated as an accessbility element.': string),
render: function (): React.Node {
return <Image accessible source={fullImage} style={styles.base} />;
},
},
{
title: 'Accessibility Label',
description:
('When an element is marked as accessibile (using the accessibility prop), it is good practice to set an accessibilityLabel on the image to provide a description of the element to people who use VoiceOver. VoiceOver will read this string when people select this element.': string),
render: function (): React.Node {
return (
<Image
accessible
accessibilityLabel="Picture of people standing around a table"
source={fullImage}
style={styles.base}
/>
);
},
},
{
title: 'Accessibility Label via alt prop',
description:
'Using the alt prop markes an element as being accessibile, and passes the alt text to accessibilityLabel',
render: function (): React.Node {
return (
<Image
alt="Picture of people standing around a table"
source={fullImage}
style={styles.base}
/>
);
},
},
{
title: 'Fade Duration',
description:
('The time (in miliseconds) that an image will fade in for when it appears (default = 300).': string),
render: function (): React.Node {
return <FadeDurationExample />;
},
platform: 'android',
},
{
title: 'Loading Indicator Source',
description:
('This prop is used to set the resource that will be used as the loading indicator for the image (displayed until the image is ready to be displayed).': string),
render: function (): React.Node {
return <LoadingIndicatorSourceExample />;
},
},
{
title: 'On Layout',
description:
('This prop is used to set the handler function to be called when the image is mounted or its layout changes. The function receives an event with `{nativeEvent: {layout: {x, y, width, height}}}`': string),
render: function (): React.Node {
return <OnLayoutExample />;
},
},
{
title: 'On Partial Load',
description:
('This prop is used to set the handler function to be called when the partial load of the image is complete. This is meant for progressive JPEG loads.': string),
render: function (): React.Node {
return <OnPartialLoadExample />;
},
platform: 'ios',
},
{
title: 'Vector Drawable',
name: 'vector-drawable',
description:
'Demonstrating an example of loading a vector drawable asset by name',
render: function (): React.Node {
return <VectorDrawableExample />;
},
platform: 'android',
},
{
title: 'Large image with different resize methods',
name: 'resize-method',
description:
'Demonstrating the effects of loading a large image with different resize methods',
scrollable: true,
render: function (): React.Node {
const methods: Array<ImageProps['resizeMethod']> = [
'auto',
'resize',
'scale',
'none',
];
// Four copies of the same image so we don't serve cached copies of the same image
const images = [
require('../../assets/large-image-1.png'),
require('../../assets/large-image-2.png'),
require('../../assets/large-image-3.png'),
require('../../assets/large-image-4.png'),
];
return (
<View testID="resize-method-example">
{methods.map((method, index) => (
<View
key={method}
style={{display: 'flex', overflow: 'hidden'}}
testID={`resize-method-example-${method ?? ''}`}>
<RNTesterText>{method}</RNTesterText>
<Image
resizeMethod={method}
source={images[index]}
style={styles.resizedImage}
/>
</View>
))}
</View>
);
},
platform: 'android',
},
] as Array<RNTesterModuleExample>;