18

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?

Ivan Chernykh
  • 41,617
  • 13
  • 134
  • 146

7 Answers7

27

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",
    }
});
LeonHeart
  • 539
  • 8
  • 10
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,
  }
})
marcel
  • 555
  • 6
  • 12
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.

bearbyt3z
  • 75
  • 1
  • 6
1

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.

David Schumann
  • 13,380
  • 9
  • 75
  • 96
d-vine
  • 5,317
  • 3
  • 27
  • 25
0

Yes, deafultSource and loadingIndicatorSource is not working properly. Also image component cannot contain children. Try this solutions => https://stackoverflow.com/a/62510268/11302100

Nijat Aliyev
  • 558
  • 6
  • 15
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>
Harsh Kukarwadiya
  • 423
  • 1
  • 3
  • 12
-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} />}
/>
Lonare
  • 3,581
  • 1
  • 41
  • 45
  • your answer is not for the actual react-native image, its for a separated library react-native-elements – Anas Mar 25 '23 at 14:50