Nico
Nico

Reputation: 6359

Load a local image after loading a remote image failed

Is it possible to load a local image if the remote image failed?

For example, I have the following code:

<Image style={ styles.userImage }
       source={ { uri: http://example.com/my_image.jpg } }
       onError={(error) => ...}
/>

In case for example I don't have the rights to access http://example.com/my_image.jpg, I'll get an error in onError. Is there a way then to load a local image instead?

Upvotes: 23

Views: 25820

Answers (4)

Ho&#224;ng Vũ Anh
Ho&#224;ng Vũ Anh

Reputation: 677

Create a component ImageLoad like this:

import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import { Image, ImageBackground, ActivityIndicator, View } from 'react-native';

class ImageLoad extends PureComponent {
  static propTypes = {
    isShowActivity: PropTypes.bool,
  };

  static defaultProps = {
    isShowActivity: true,
    };

  constructor(props) {
    super(props);
    this.state = {
      isLoaded: false,
      isError: false
    };
  }

  onLoadEnd(){
    this.setState({
      isLoaded: true
    });
  }

  onError(){
    this.setState({
      isError: true
    });
  }

  render() {
    const {
      style, source, resizeMode, borderRadius, backgroundColor, children,
      loadingStyle, placeholderSource, placeholderStyle,
      customImagePlaceholderDefaultStyle
    } = this.props;
    return(
      <ImageBackground
        onLoadEnd={this.onLoadEnd.bind(this)}
        onError={this.onError.bind(this)}
        style={[styles.backgroundImage, style]}
        source={source}
        resizeMode={resizeMode}
        borderRadius={borderRadius}
      >
        {
          (this.state.isLoaded && !this.state.isError) ? children :
          <View
            style={[styles.viewImageStyles, { borderRadius: borderRadius }, backgroundColor ? { backgroundColor: backgroundColor } : {}]}
          >
            {
              (this.props.isShowActivity && !this.state.isError) &&
              <ActivityIndicator
                style={styles.activityIndicator}
                size={loadingStyle ? loadingStyle.size : 'small'}
                color={loadingStyle ? loadingStyle.color : 'gray'}
              />
            }
            <Image
              style={placeholderStyle ? placeholderStyle : [styles.imagePlaceholderStyles, customImagePlaceholderDefaultStyle]}
              source={placeholderSource ? placeholderSource : require('./Images/empty-image.png')}
            >
            </Image>
          </View>
        }
        {
          this.props.children &&
          <View style={styles.viewChildrenStyles}>
          {
            this.props.children
          }
          </View>
        }
      </ImageBackground>
    );
  }
}

const styles = {
  backgroundImage: {
    position: 'relative',
  },
  activityIndicator: {
    position: 'absolute',
    margin: 'auto',
    zIndex: 9,
  },
  viewImageStyles: {
    flex: 1,
    backgroundColor: '#e9eef1',
    justifyContent: 'center',
    alignItems: 'center'
  },
  imagePlaceholderStyles: {
    width: 100,
    height: 100,
    resizeMode: 'contain',
    justifyContent: 'center',
    alignItems: 'center'
  },
  viewChildrenStyles: {
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    position: 'absolute',
    backgroundColor: 'transparent'
  }
}

export default ImageLoad;

and use this anywhere in your app:

<ImageLoad
    style={{ width: 320, height: 250 }}
    loadingStyle={{ size: 'large', color: 'blue' }}
    source={{ uri: 'url image' }}
/>

Upvotes: 0

Tyler
Tyler

Reputation: 1662

To elaborate on Cherniv's answer you could create an <Images /> component that abstracts this away for you:

import React from 'react';
import { Image } from 'react-native';

export default class Images extends React.Component {
  static defaultProps = {
    source: [],
    onError: () => {},
  }

  state = { current: 0 }

  onError = error => {
    this.props.onError(error);
    const next = this.state.current + 1;
    if (next < this.props.source.length) {
      this.setState({ current: next });
    }
  }

  render() {
    const { onError, source, ...rest } = this.props;
    return (
      <Image
        source={source[this.state.current]}
        onError={this.onError}
        {...rest}
      />
    );
  }
}

Then you can use it like this:

import Images from './Images';

<Images
  source={[
    { uri: 'http://example.com/bad_image.jpg' },
    { uri: 'http://example.com/good_image.jpg' },
    require('./default.jpg'),
  ]}
  style={{
    backgroundColor: '#ccc',
    height: 200,
    width: 200,
  }}
/>

Upvotes: 5

Vinoth Kumar
Vinoth Kumar

Reputation: 1377

As per the latest docs you can use defaultSource property. It shows the image till the original image loads, if the load fails the default image is shown Link to docs

Upvotes: 34

Ivan Chernykh
Ivan Chernykh

Reputation: 42176

Use component' state. In your constructor set initial url:

 this.state = { image: { uri: 'http://example.com/my_image.jpg' } }

Create onError handler:

 onError(error){
   this.setState({ image: require('your_local_image.path')})
 }

And then combine it all together:

 <Image style={ styles.userImage }
       source={ this.state.image }
       onError={ this.onError.bind(this) }
 />

Upvotes: 32

Related Questions