Image preloading in React Native
Asked Answered
P

7

20

I am building my first app with React Native, an app with a long list of images. I want to show a spinner instead of image while image is loading. It is sounds trivial but i didn't found a solution. I think for a spinner i suppose to use ActivityIndicatorIOS , but how am i combining it with an Image component?

<Image source={...}>
  <ActivityIndicatorIOS />
</Image>

Is this a right direction? Am i missing something?

Postpaid answered 8/2, 2016 at 9:5 Comment(0)
B
29

I will share my solution

<View>
    <Image source={{uri: this.state.avatar}} style={styles.maybeRenderImage}
                                   resizeMode={"contain"} onLoadStart={() => this.setState({loading: true})}
                                   onLoadEnd={() => {
                                       this.setState({loading: false})
    }}/>
    {this.state.loading && <LoadingView/>}
</View>

LoadingView.js

export default class LoadingView extends Component {
    render() {
        return (
            <View style={styles.container}>
                <ActivityIndicator size="small" color="#FFD700"/>
            </View>
        );
    }
}
const styles = StyleSheet.create({
    container: {
        position: "absolute",
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
        opacity: 0.7,
        backgroundColor: "black",
        justifyContent: "center",
        alignItems: "center",
    }
});
Bakemeier answered 8/9, 2018 at 2:41 Comment(2)
can this approach be use on loading image with map function?Humphreys
I think this cannot be use in the map of images, may be we will use another solutionBakemeier
D
7

Here is a complete solution to providing a custom image component with a loading activity indicator centered underneath the image:

import React, { Component } from 'react';
import { StyleSheet, View, Image, ActivityIndicator } from 'react-native';

export default class LoadableImage extends Component {
  state = {
    loading: true
  }

  render() {
    const { url } = this.props

    return (
      <View style={styles.container}>
        <Image
          onLoadEnd={this._onLoadEnd}
          source={{ uri: url }}
        />
        <ActivityIndicator
          style={styles.activityIndicator}
          animating={this.state.loading}
        />
      </View>
    )
  }

  _onLoadEnd = () => {
    this.setState({
      loading: false
    })
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  activityIndicator: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
  }
})
Dilapidate answered 22/6, 2019 at 14:38 Comment(0)
L
2

I will share my own solution based only on CSS manipulation, which in my opinion is easy to understand, and the code is pretty clean. The solution is a little similar to other answers, but doesn't require absolute position of any component, or creating any additional components.

The idea is to switch between showing an <Image> and <ActivityIndicator>, based on some state variable (isImageLoaded in the snippet below).

<View>
    <Image source={...}
        onLoad={ () => this.setState({ isImageLoaded: true }) }
        style={[styles.image, { display: (this.state.isImageLoaded ? 'flex' : 'none') }]}
    />
    <ActivityIndicator
        style={{ display: (this.state.isImageLoaded ? 'none' : 'flex') }}
    />
</View>

Also you should set image size using flex property (otherwise image will be invisible):

const styles = StyleSheet.create({
  image: {
    flex: 1,
  }
});

Note that you don't have to initiate the isImageLoaded variable to false in the constructor, because it will have undefined value and the if conditions will act as expected.

Luciolucita answered 9/2, 2020 at 23:52 Comment(1)
this is working only on android on iOS it keeps on loadingSelfeffacing
P
0

Just ran into the same issue. So basically you have the correct approach, but the indicator should of course only be rendered when the image is loading. To keep track of that you need state. To keep it simple we assume you have just on image in the component an keep the state for it in the same component. (The cool kids will argue you should use a higher order component for that and then pass the state in via a prop ;)

The idea then is, that your image starts out loading and onLoadEnd (or onLoad, but then the spinner gets stuck on error, which is fine or course) you re-set the state.

getInitialState: function(){ return { loading: true }}

render: function(){
    <Image source={...} onLoadEnd={ ()=>{ this.setState({ loading: false }) }>
        <ActivityIndicatorIOS animating={ this.state.loading }/>
    </Image>
}

You could also start out with { loading: false } and set it true onLoadStart, but I'm not sure what the benefit would be of that.

Also for styling reasons, depending on your layout, you might need to put the indicator in a container view that is absolutely positioned. But you get the idea.

Pinxit answered 8/2, 2016 at 16:48 Comment(4)
This answer doesn't make any sense. <Image /> cannot contain children.Carrasco
Image cannot contain childrenStairway
The <Image> component cannot contain childrenAlcus
If one really needs to obtain a children inside an image try <ImageBackground><ActivityIndicator /></ImageBackground>Godderd
F
0

Yes, deafultSource and loadingIndicatorSource is not working properly. Also image component cannot contain children. Try this solutions => https://mcmap.net/q/662880/-show-loader-until-live-link-image-does-not-load-completely-react-native-0-58

Fabe answered 22/6, 2020 at 8:34 Comment(0)
P
0

  const [imageLoading, setIsImageLoading] = useState(true);

    <View>
      <View
        style={[
          {justifyContent: 'center', alignItems: 'center'},
          imageLoading ? {display: 'flex'} : {display: 'none'},
        ]}>
        <ActivityIndicator />
      </View>
      <Image
        source={{uri: ""}}
        onLoadEnd={() => {setIsImageLoading(false)}}
        style={[
          imageStyle,
          imageLoading ? {display: 'none'} : {display: 'flex'},
        ]}
      />
    </View>
Panfish answered 9/8, 2022 at 11:47 Comment(0)
L
-1

You can simply just add a placeholder

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

<Image 
 style={styles(colors).thumbnail} 
 source={{ uri: event.image }} 
 PlaceholderContent={<ActivityIndicator color={colors.indicator} />}
/>
Linet answered 9/8, 2022 at 9:38 Comment(1)
your answer is not for the actual react-native image, its for a separated library react-native-elementsNeigh

© 2022 - 2024 — McMap. All rights reserved.