Sometimes, you don't necessarily want an image to load at the exact moment that it's rendered. For example, you might be rendering something that's not yet visible on the screen. Most of the time, it's perfectly fine to fetch the image source from the network before it's actually visible. But if you're fine-tuning your application and discovering that loading lots of images over the network causes performance issues, you can lazily load the source.
I think the more common case in a mobile context is handling a scenario where you've rendered one or more images where they're visible, but the network is slow to respond. In this case, you will probably want to render a placeholder image so that the user sees something right away, rather than empty space.
To do this, we'll implement an abstraction that wraps the actual image that we want to show once it's loaded. Here's the code:
import React, { Component, PropTypes } from 'react'; import { View, Image } from 'react-native'; // The local placeholder image source. const placeholder = require('./images/placeholder.png'); // The mapping to the "loaded" state that gets us // the appropriate image component. const Placeholder = props => new Map([ [true, null], [false, ( <Image {...props} source={placeholder} /> )], ]).get(props.loaded); class LazyImage extends Component { // The "width" and "height" properties // are required. All other properties are // forwarded to the actual "<Image>" // component. static propTypes = { style: PropTypes.shape({ width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, }), }; constructor() { super(); // We assume that the source hasn't finished // loading yet. this.state = { loaded: false, }; } render() { // The props and state this component // needs in order to render... const { props: { style: { width, height, }, }, state: { loaded, }, } = this; return ( <View style={{ width, height }}> { /* The placeholder image is just a standard "<Image>" component with a predefined source. It isn't rendered if "loaded" is true. */ } <Placeholder loaded={loaded} {...this.props} /> { /* The actual image is forwarded props that are passed to "<LazyImage>". The "onLoad" handler ensures the "loaded" state is true, removing the placeholder image. */ } <Image {...this.props} onLoad={() => this.setState({ loaded: true, })} /> </View> ); } } export default LazyImage;
This component renders a View
with two Image
components inside. It also has a loaded
state, which is initially false. When loaded
is false, the placeholder image is rendered. The loaded
state is set to true when the onLoad()
handler is called. This means that the placeholder image is removed, and the main image is displayed.
Now let's use the LazyImage
component that we've just implemented. We'll render the image without a source, and the placeholder image should be displayed. We'll add a button that gives the lazy image a source, and when it loads, the placeholder image should be replaced. Here's what the main app module looks like:
/* eslint-disable global-require */ import React, { Component } from 'react'; import { AppRegistry, View, } from 'react-native'; import styles from './styles'; import LazyImage from './LazyImage'; import Button from './Button'; // The remote image to load... const remote = 'https://facebook.github.io/react/img/logo_small_2x.png'; class LazyLoading extends Component { state = { source: null, } render() { return ( <View style={styles.container}> { /* Renders the lazy image. Since there's no "source" value initially, the placeholder image will be rendered. */ } <LazyImage style={{ width: 200, height: 100 }} resizeMode="contain" source={this.state.source} /> { /* When pressed, this button changes the "source" of the lazy image. When the new source loads, the placeholder image is replaced. */ } <Button label="Load Remote" onPress={() => this.setState({ source: { uri: remote }, })} /> </View> ); } } AppRegistry.registerComponent( 'LazyLoading', () => LazyLoading );
This is what the screen looks like initially:
Then, if you click the Load Remote button, you'll eventually see the image that we actually want:
You might notice that depending on your network speed, the placeholder image remains visible even after you click the Load Remote button. This is by design, because you don't want to remove the placeholder image until you know for sure that the actual image is ready to be displayed.
3.135.183.89