Skip to content

Adding 'defaultSource' for image source in case of an error #1116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion generatedTypes/components/chip/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ declare const _default: React.ComponentClass<ViewProps & Pick<import("react-nati
/**
* Additional icon props
*/
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;
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;
/**
* Icon style
*/
Expand Down
21 changes: 19 additions & 2 deletions generatedTypes/components/image/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { PureComponent } from 'react';
import { ImageProps as RNImageProps, ImageSourcePropType } from 'react-native';
import { ImageProps as RNImageProps, ImageSourcePropType, NativeSyntheticEvent, ImageErrorEventData } from 'react-native';
import { ForwardRefInjectedProps, BaseComponentInjectedProps, MarginModifiers } from '../../commons/new';
import { OverlayTypeType } from '../overlay';
export declare type ImageProps = RNImageProps & MarginModifiers & {
Expand Down Expand Up @@ -44,14 +44,22 @@ export declare type ImageProps = RNImageProps & MarginModifiers & {
* Render an overlay with custom content
*/
customOverlayContent?: JSX.Element;
/**
* Default image source in case of an error
*/
errorSource?: ImageSourcePropType;
};
declare type Props = ImageProps & ForwardRefInjectedProps & BaseComponentInjectedProps;
declare type State = {
error: boolean;
prevSource: ImageSourcePropType;
};
/**
* @description: Image wrapper with extra functionality like source transform and assets support
* @extends: Image
* @extendslink: https://facebook.github.io/react-native/docs/image.html
*/
declare class Image extends PureComponent<Props> {
declare class Image extends PureComponent<Props, State> {
static displayName: string;
static defaultProps: {
assetGroup: string;
Expand All @@ -64,9 +72,14 @@ declare class Image extends PureComponent<Props> {
};
sourceTransformer?: (props: any) => ImageSourcePropType;
constructor(props: Props);
static getDerivedStateFromProps(nextProps: Partial<Props>, prevState: State): {
error: boolean;
} | null;
isGif(): boolean | undefined;
shouldUseImageBackground(): boolean;
getVerifiedSource(source?: ImageSourcePropType): any;
getImageSource(): any;
onError: (event: NativeSyntheticEvent<ImageErrorEventData>) => void;
render(): JSX.Element;
}
export { Image };
Expand Down Expand Up @@ -112,6 +125,10 @@ declare const _default: React.ComponentClass<RNImageProps & Partial<Record<"marg
* Render an overlay with custom content
*/
customOverlayContent?: JSX.Element | undefined;
/**
* Default image source in case of an error
*/
errorSource?: number | import("react-native").ImageURISource | import("react-native").ImageURISource[] | undefined;
} & {
useCustomTheme?: boolean | undefined;
}, any>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,7 @@ exports[`Button container size should have no padding of button is an icon butto
accessibilityRole="image"
accessible={false}
assetGroup="icons"
onError={[Function]}
source={14}
style={
Array [
Expand Down Expand Up @@ -1657,6 +1658,7 @@ exports[`Button icon should apply color on icon 1`] = `
accessibilityRole="image"
accessible={false}
assetGroup="icons"
onError={[Function]}
source={12}
style={
Array [
Expand Down Expand Up @@ -1709,6 +1711,7 @@ exports[`Button icon should apply color on icon 2`] = `
accessibilityRole="image"
accessible={false}
assetGroup="icons"
onError={[Function]}
source={12}
style={
Array [
Expand Down Expand Up @@ -1761,6 +1764,7 @@ exports[`Button icon should include custom iconStyle provided as a prop 1`] = `
accessibilityRole="image"
accessible={false}
assetGroup="icons"
onError={[Function]}
source={12}
style={
Array [
Expand Down Expand Up @@ -1818,6 +1822,7 @@ exports[`Button icon should return icon style according to different variations
accessibilityRole="image"
accessible={false}
assetGroup="icons"
onError={[Function]}
source={12}
style={
Array [
Expand Down Expand Up @@ -1870,6 +1875,7 @@ exports[`Button icon should return icon style according to different variations
accessibilityRole="image"
accessible={false}
assetGroup="icons"
onError={[Function]}
source={12}
style={
Array [
Expand Down Expand Up @@ -1922,6 +1928,7 @@ exports[`Button icon should return icon style according to different variations
accessibilityRole="image"
accessible={false}
assetGroup="icons"
onError={[Function]}
source={12}
style={
Array [
Expand Down Expand Up @@ -2875,6 +2882,7 @@ exports[`Button labelColor should return undefined color if this is an icon butt
accessibilityRole="image"
accessible={false}
assetGroup="icons"
onError={[Function]}
source={12}
style={
Array [
Expand Down
64 changes: 53 additions & 11 deletions src/components/image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ import _ from 'lodash';
import React, {PureComponent} from 'react';
//@ts-ignore
import hoistNonReactStatic from 'hoist-non-react-statics';
import {Image as RNImage, ImageProps as RNImageProps, StyleSheet, ImageBackground, ImageSourcePropType} from 'react-native';
import {
StyleSheet,
Image as RNImage,
ImageProps as RNImageProps,
ImageBackground,
ImageSourcePropType,
NativeSyntheticEvent,
ImageErrorEventData
} from 'react-native';
import Constants from '../../helpers/Constants';
import {asBaseComponent, ForwardRefInjectedProps, BaseComponentInjectedProps, MarginModifiers} from '../../commons/new';
// @ts-ignore
import Assets from '../../assets';
import Overlay, {OverlayTypeType} from '../overlay';


export type ImageProps = RNImageProps & MarginModifiers & {
/**
* custom source transform handler for manipulating the image source (great for size control)
Expand Down Expand Up @@ -51,16 +60,25 @@ export type ImageProps = RNImageProps & MarginModifiers & {
* Render an overlay with custom content
*/
customOverlayContent?: JSX.Element;
/**
* Default image source in case of an error
*/
errorSource?: ImageSourcePropType
};

type Props = ImageProps & ForwardRefInjectedProps & BaseComponentInjectedProps;

type State = {
error: boolean,
prevSource: ImageSourcePropType
}

/**
* @description: Image wrapper with extra functionality like source transform and assets support
* @extends: Image
* @extendslink: https://facebook.github.io/react-native/docs/image.html
*/
class Image extends PureComponent<Props> {
class Image extends PureComponent<Props, State> {
static displayName = 'Image';

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

this.sourceTransformer = this.props.sourceTransformer;

this.state = {
error: false,
prevSource: props.source
};
}

static getDerivedStateFromProps(nextProps: Partial<Props>, prevState: State) {
if (nextProps.source !== prevState.prevSource) {
return {
error: false
};
}
return null;
}

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

getVerifiedSource(source?: ImageSourcePropType) {
if (_.get(source, 'uri') === null || _.get(source, 'uri') === '') {
// @ts-ignore
return {...source, uri: undefined};
}
return source;
}

getImageSource() {
const {assetName, assetGroup} = this.props;
const {assetName, assetGroup, source} = this.props;

if (!_.isUndefined(assetName)) {
return _.get(Assets, `${assetGroup}.${assetName}`);
}

if (this.sourceTransformer) {
return this.sourceTransformer(this.props);
}

const {source} = this.props;
if (_.get(source, 'uri') === null || _.get(source, 'uri') === '') {
// @ts-ignore
return {...source, uri: undefined};
}
return this.getVerifiedSource(source);
}

return source;
onError = (event: NativeSyntheticEvent<ImageErrorEventData>) => {
if (event.nativeEvent.error) {
this.setState({error: true});
}
}

render() {
const source = this.getImageSource();
const {error} = this.state;
const source = error ? this.getVerifiedSource(this.props.errorSource) : this.getImageSource();
const {
tintColor,
style,
Expand Down Expand Up @@ -144,6 +185,7 @@ class Image extends PureComponent<Props> {
accessible={false}
accessibilityRole={'image'}
{...others}
onError={this.onError}
source={source}
>
{(overlayType || customOverlayContent) && (
Expand Down