Skip to content

Commit fb60423

Browse files
authored
Adding 'defaultSource' for image source in case of an error (#1116)
* Adding 'defaultSource' for image source in case of an error * Fix prop type for 'getDerivedStateFromProps()' parameters * Change prop name to 'errorSource'. Validate source. * generating types * updating snapshot test
1 parent e483926 commit fb60423

File tree

4 files changed

+81
-14
lines changed

4 files changed

+81
-14
lines changed

generatedTypes/components/chip/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ declare const _default: React.ComponentClass<ViewProps & Pick<import("react-nati
313313
/**
314314
* Additional icon props
315315
*/
316-
iconProps?: Pick<ImageProps, "margin" | "marginL" | "marginT" | "marginR" | "marginB" | "marginH" | "marginV" | "style" | "testID" | "onLayout" | "accessible" | "accessibilityActions" | "accessibilityLabel" | "accessibilityRole" | "accessibilityState" | "accessibilityHint" | "accessibilityValue" | "onAccessibilityAction" | "accessibilityComponentType" | "accessibilityLiveRegion" | "importantForAccessibility" | "accessibilityElementsHidden" | "accessibilityTraits" | "accessibilityViewIsModal" | "onAccessibilityEscape" | "onAccessibilityTap" | "onMagicTap" | "accessibilityIgnoresInvertColors" | "width" | "height" | "borderRadius" | "borderBottomLeftRadius" | "borderBottomRightRadius" | "borderTopLeftRadius" | "borderTopRightRadius" | "aspectRatio" | "onError" | "onLoad" | "onLoadEnd" | "onLoadStart" | "progressiveRenderingEnabled" | "resizeMode" | "resizeMethod" | "loadingIndicatorSource" | "defaultSource" | "blurRadius" | "capInsets" | "onProgress" | "onPartialLoad" | "fadeDuration" | "cover" | "sourceTransformer" | "assetName" | "assetGroup" | "tintColor" | "supportRTL" | "overlayType" | "overlayColor" | "customOverlayContent"> | undefined;
316+
iconProps?: Pick<ImageProps, "margin" | "marginL" | "marginT" | "marginR" | "marginB" | "marginH" | "marginV" | "style" | "testID" | "onLayout" | "accessible" | "accessibilityActions" | "accessibilityLabel" | "accessibilityRole" | "accessibilityState" | "accessibilityHint" | "accessibilityValue" | "onAccessibilityAction" | "accessibilityComponentType" | "accessibilityLiveRegion" | "importantForAccessibility" | "accessibilityElementsHidden" | "accessibilityTraits" | "accessibilityViewIsModal" | "onAccessibilityEscape" | "onAccessibilityTap" | "onMagicTap" | "accessibilityIgnoresInvertColors" | "width" | "height" | "borderRadius" | "borderBottomLeftRadius" | "borderBottomRightRadius" | "borderTopLeftRadius" | "borderTopRightRadius" | "aspectRatio" | "onError" | "onLoad" | "onLoadEnd" | "onLoadStart" | "progressiveRenderingEnabled" | "resizeMode" | "resizeMethod" | "loadingIndicatorSource" | "defaultSource" | "blurRadius" | "capInsets" | "onProgress" | "onPartialLoad" | "fadeDuration" | "cover" | "sourceTransformer" | "assetName" | "assetGroup" | "tintColor" | "supportRTL" | "overlayType" | "overlayColor" | "customOverlayContent" | "errorSource"> | undefined;
317317
/**
318318
* Icon style
319319
*/

generatedTypes/components/image/index.d.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { PureComponent } from 'react';
2-
import { ImageProps as RNImageProps, ImageSourcePropType } from 'react-native';
2+
import { ImageProps as RNImageProps, ImageSourcePropType, NativeSyntheticEvent, ImageErrorEventData } from 'react-native';
33
import { ForwardRefInjectedProps, BaseComponentInjectedProps, MarginModifiers } from '../../commons/new';
44
import { OverlayTypeType } from '../overlay';
55
export declare type ImageProps = RNImageProps & MarginModifiers & {
@@ -44,14 +44,22 @@ export declare type ImageProps = RNImageProps & MarginModifiers & {
4444
* Render an overlay with custom content
4545
*/
4646
customOverlayContent?: JSX.Element;
47+
/**
48+
* Default image source in case of an error
49+
*/
50+
errorSource?: ImageSourcePropType;
4751
};
4852
declare type Props = ImageProps & ForwardRefInjectedProps & BaseComponentInjectedProps;
53+
declare type State = {
54+
error: boolean;
55+
prevSource: ImageSourcePropType;
56+
};
4957
/**
5058
* @description: Image wrapper with extra functionality like source transform and assets support
5159
* @extends: Image
5260
* @extendslink: https://facebook.github.io/react-native/docs/image.html
5361
*/
54-
declare class Image extends PureComponent<Props> {
62+
declare class Image extends PureComponent<Props, State> {
5563
static displayName: string;
5664
static defaultProps: {
5765
assetGroup: string;
@@ -64,9 +72,14 @@ declare class Image extends PureComponent<Props> {
6472
};
6573
sourceTransformer?: (props: any) => ImageSourcePropType;
6674
constructor(props: Props);
75+
static getDerivedStateFromProps(nextProps: Partial<Props>, prevState: State): {
76+
error: boolean;
77+
} | null;
6778
isGif(): boolean | undefined;
6879
shouldUseImageBackground(): boolean;
80+
getVerifiedSource(source?: ImageSourcePropType): any;
6981
getImageSource(): any;
82+
onError: (event: NativeSyntheticEvent<ImageErrorEventData>) => void;
7083
render(): JSX.Element;
7184
}
7285
export { Image };
@@ -112,6 +125,10 @@ declare const _default: React.ComponentClass<RNImageProps & Partial<Record<"marg
112125
* Render an overlay with custom content
113126
*/
114127
customOverlayContent?: JSX.Element | undefined;
128+
/**
129+
* Default image source in case of an error
130+
*/
131+
errorSource?: number | import("react-native").ImageURISource | import("react-native").ImageURISource[] | undefined;
115132
} & {
116133
useCustomTheme?: boolean | undefined;
117134
}, any>;

src/components/button/__tests__/__snapshots__/index.spec.js.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,7 @@ exports[`Button container size should have no padding of button is an icon butto
852852
accessibilityRole="image"
853853
accessible={false}
854854
assetGroup="icons"
855+
onError={[Function]}
855856
source={14}
856857
style={
857858
Array [
@@ -1657,6 +1658,7 @@ exports[`Button icon should apply color on icon 1`] = `
16571658
accessibilityRole="image"
16581659
accessible={false}
16591660
assetGroup="icons"
1661+
onError={[Function]}
16601662
source={12}
16611663
style={
16621664
Array [
@@ -1709,6 +1711,7 @@ exports[`Button icon should apply color on icon 2`] = `
17091711
accessibilityRole="image"
17101712
accessible={false}
17111713
assetGroup="icons"
1714+
onError={[Function]}
17121715
source={12}
17131716
style={
17141717
Array [
@@ -1761,6 +1764,7 @@ exports[`Button icon should include custom iconStyle provided as a prop 1`] = `
17611764
accessibilityRole="image"
17621765
accessible={false}
17631766
assetGroup="icons"
1767+
onError={[Function]}
17641768
source={12}
17651769
style={
17661770
Array [
@@ -1818,6 +1822,7 @@ exports[`Button icon should return icon style according to different variations
18181822
accessibilityRole="image"
18191823
accessible={false}
18201824
assetGroup="icons"
1825+
onError={[Function]}
18211826
source={12}
18221827
style={
18231828
Array [
@@ -1870,6 +1875,7 @@ exports[`Button icon should return icon style according to different variations
18701875
accessibilityRole="image"
18711876
accessible={false}
18721877
assetGroup="icons"
1878+
onError={[Function]}
18731879
source={12}
18741880
style={
18751881
Array [
@@ -1922,6 +1928,7 @@ exports[`Button icon should return icon style according to different variations
19221928
accessibilityRole="image"
19231929
accessible={false}
19241930
assetGroup="icons"
1931+
onError={[Function]}
19251932
source={12}
19261933
style={
19271934
Array [
@@ -2875,6 +2882,7 @@ exports[`Button labelColor should return undefined color if this is an icon butt
28752882
accessibilityRole="image"
28762883
accessible={false}
28772884
assetGroup="icons"
2885+
onError={[Function]}
28782886
source={12}
28792887
style={
28802888
Array [

src/components/image/index.tsx

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@ import _ from 'lodash';
22
import React, {PureComponent} from 'react';
33
//@ts-ignore
44
import hoistNonReactStatic from 'hoist-non-react-statics';
5-
import {Image as RNImage, ImageProps as RNImageProps, StyleSheet, ImageBackground, ImageSourcePropType} from 'react-native';
5+
import {
6+
StyleSheet,
7+
Image as RNImage,
8+
ImageProps as RNImageProps,
9+
ImageBackground,
10+
ImageSourcePropType,
11+
NativeSyntheticEvent,
12+
ImageErrorEventData
13+
} from 'react-native';
614
import Constants from '../../helpers/Constants';
715
import {asBaseComponent, ForwardRefInjectedProps, BaseComponentInjectedProps, MarginModifiers} from '../../commons/new';
816
// @ts-ignore
917
import Assets from '../../assets';
1018
import Overlay, {OverlayTypeType} from '../overlay';
1119

20+
1221
export type ImageProps = RNImageProps & MarginModifiers & {
1322
/**
1423
* custom source transform handler for manipulating the image source (great for size control)
@@ -51,16 +60,25 @@ export type ImageProps = RNImageProps & MarginModifiers & {
5160
* Render an overlay with custom content
5261
*/
5362
customOverlayContent?: JSX.Element;
63+
/**
64+
* Default image source in case of an error
65+
*/
66+
errorSource?: ImageSourcePropType
5467
};
5568

5669
type Props = ImageProps & ForwardRefInjectedProps & BaseComponentInjectedProps;
5770

71+
type State = {
72+
error: boolean,
73+
prevSource: ImageSourcePropType
74+
}
75+
5876
/**
5977
* @description: Image wrapper with extra functionality like source transform and assets support
6078
* @extends: Image
6179
* @extendslink: https://facebook.github.io/react-native/docs/image.html
6280
*/
63-
class Image extends PureComponent<Props> {
81+
class Image extends PureComponent<Props, State> {
6482
static displayName = 'Image';
6583

6684
static defaultProps = {
@@ -75,6 +93,20 @@ class Image extends PureComponent<Props> {
7593
super(props);
7694

7795
this.sourceTransformer = this.props.sourceTransformer;
96+
97+
this.state = {
98+
error: false,
99+
prevSource: props.source
100+
};
101+
}
102+
103+
static getDerivedStateFromProps(nextProps: Partial<Props>, prevState: State) {
104+
if (nextProps.source !== prevState.prevSource) {
105+
return {
106+
error: false
107+
};
108+
}
109+
return null;
78110
}
79111

80112
isGif() {
@@ -92,27 +124,36 @@ class Image extends PureComponent<Props> {
92124
return !!overlayType || this.isGif() || !_.isUndefined(customOverlayContent);
93125
}
94126

127+
getVerifiedSource(source?: ImageSourcePropType) {
128+
if (_.get(source, 'uri') === null || _.get(source, 'uri') === '') {
129+
// @ts-ignore
130+
return {...source, uri: undefined};
131+
}
132+
return source;
133+
}
134+
95135
getImageSource() {
96-
const {assetName, assetGroup} = this.props;
136+
const {assetName, assetGroup, source} = this.props;
137+
97138
if (!_.isUndefined(assetName)) {
98139
return _.get(Assets, `${assetGroup}.${assetName}`);
99140
}
100-
101141
if (this.sourceTransformer) {
102142
return this.sourceTransformer(this.props);
103143
}
104144

105-
const {source} = this.props;
106-
if (_.get(source, 'uri') === null || _.get(source, 'uri') === '') {
107-
// @ts-ignore
108-
return {...source, uri: undefined};
109-
}
145+
return this.getVerifiedSource(source);
146+
}
110147

111-
return source;
148+
onError = (event: NativeSyntheticEvent<ImageErrorEventData>) => {
149+
if (event.nativeEvent.error) {
150+
this.setState({error: true});
151+
}
112152
}
113153

114154
render() {
115-
const source = this.getImageSource();
155+
const {error} = this.state;
156+
const source = error ? this.getVerifiedSource(this.props.errorSource) : this.getImageSource();
116157
const {
117158
tintColor,
118159
style,
@@ -144,6 +185,7 @@ class Image extends PureComponent<Props> {
144185
accessible={false}
145186
accessibilityRole={'image'}
146187
{...others}
188+
onError={this.onError}
147189
source={source}
148190
>
149191
{(overlayType || customOverlayContent) && (

0 commit comments

Comments
 (0)