Image preloading in React Native - image

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?

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",
}
});

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,
}
})

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.

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.

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

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} />}
/>

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>

Related

React Native Expo (Image Picker) is not displaying the Images

Please help me out with this problem.
I can able to pick the image from my Local storage. In the Console also it is showing, But I cannot able to display it on the screen.
Here is my code.
import * as ImagePicker from "expo-image-picker";
import React, { useState } from "react";
import {
ActivityIndicator,
Button,
FlatList,
Image,
StyleSheet,
Text,
useWindowDimensions,
View
} from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
export default function App() {
const [images, setImages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const pickImages = async () => {
// No permissions request is necessary for launching the image library
setIsLoading(true);
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
// allowsEditing: true,
allowsMultipleSelection: true,
selectionLimit: 10,
aspect: [4, 3],
quality: 1,
});
setIsLoading(false);
console.log(result);
if (!result.canceled) {
setImages(result.uri ? [result.uri] : result.selected);
}
};
return (
<>
<FlatList
data={images}
renderItem={({ item }) => (
<Image
source={{ uri: item.uri }}
style={{ width: 100, height: 100 }}
/>
)}
keyExtractor={(item) => item.uri}
contentContainerStyle={{ marginVertical: 50, paddingBottom: 50 }}
ListHeaderComponent={
isLoading ? (
<View>
<Text
style={{ fontSize: 20, fontWeight: "bold", textAlign: "center" }}
>
Loading...
</Text>
<ActivityIndicator size={"large"} />
</View>
) : (
<Button title="Pick images" onPress={pickImages} />
)
}
/>
</>
);
}
I can able to pick the image from my Local storage. In Console also it is showing, But i cannot able to display it in screen.
Kindly help me out.
It looks like you're already passing the uri into the array. by doing [result.uri] and then in the image you're doing it again item.uri. trying just doing the item.

Image not loading in React Native when I use require but loads when I load from URL

When I try to load an image by using require, the image does not load but when I load the same image from a URL, the image loads. Here is the snippet of code that I am calling the image from
class Home extends React.Component {
render() {
return (
<ScrollView
noSpacer={true}
noScroll={true}
style={styles.container}
showVerticalSCrollIndicator = {false}
showHorizontalScrollIndicator = {false}
>
{this.state.loading ? (
<ActivityIndicator
style={[styles.centering, styles.gray]}
color="#5d38aa"
size="large"
/>
) : (
<div>
<Header title={this.state.user.name} />
<div id='image'>
<Image
source={require('./arrow.png')}
style={styles.image}
/>
</div>
</div>
)}
</ScrollView>
);
}
}
The image is loaded here
<Image
source={require('./arrow.png')}
style={styles.image}
/>
Please make sure that you give the right path to your image.
You can use the source as an object:
<Image source={{ uri: 'something.jpg' }} />
But what you have should work, check your path.
There were few errors here and there, I think you were trying to port ReactJS code to RN and not surprisingly there were few slip-ups like you used div instead and View and small things like that, also boxShadow was not working so I removed that.
After a few tweaks code is working and images are loading.
As I stated earlier, I have omitted the User component and sanityClient, you can implement them later.
Here is the working home.js after changes.
import React from "react";
import {
ScrollView,
ActivityIndicator,
StyleSheet,
Image,
ImageBackground,
View,
} from "react-native";
// import UserList from "./user-list";
import Header from "./header";
// import sanityClient from "";
// import BackButton from "./back-button";
// import User from "./user";
// import {Asset} from 'expo-asset';
// const imageURI = Asset.fromModule(require('./arrow.png')).uri;
// const image = require("./assets/aoeu.jpg");
class Home extends React.Component {
state = {
user: {},
loading: true,
};
componentDidMount() {
// TODO: get users
this.getUser();
}
async getUser() {
// sanityClient
// .fetch(
// `*[ _type == "user" && emailAddress.current == "dwight#viamaven.com"]`
// )
// .then((data) => {
// console.log(data);
// this.setState({ user: data[0], loading: false });
// console.log(this.state.user);
// })
// .catch((err) => console.error(err));
// const res = await fetch("https://randomuser.me/api/?results=20");
// const { results} = await res.json();
// // console.log(results)
// this.setState({users: [...results], loading: false});
}
render() {
return (
<ScrollView
noSpacer={true}
noScroll={true}
style={styles.container}
showVerticalSCrollIndicator={false}
showHorizontalScrollIndicator={false}
>
{!this.state.loading ? (
<ActivityIndicator
style={[styles.centering, styles.gray]}
color="#5d38aa"
size="large"
/>
) : (
<View>
<Header title={"Spidy"} />
<View id="image">
<Image source={require("./arrow.png")} style={styles.image} />
</View>
{/* <User /> */}
</View>
)}
</ScrollView>
);
}
}
var styles = StyleSheet.create({
container: {
backgroundColor: "white",
width: 375,
height: 812,
// top: '50px',
},
centering: {
alignItems: "center",
justifyContent: "center",
padding: 8,
height: "100vh",
},
image: {
width: 50,
height: 50,
marginRight: 20,
// boxShadow: "0px 1px 2px 0px rgba(0,0,0,0.1)",
// boxShadow: "10px 10px 17px -12px rgba(0,0,0,0.75)",
},
});
export default Home;
Zip file containing all the changes: src
Output:

Replace with a text while encountering onError on Image REACT NATIVE

Just like HTML, we have the opportunity to show a text i.e.
<img src="hello.png" alt="hello" />
There is plenty of suggestions for replacing an image with another image (fallback src), but I need to show a text instead of any other images!
React Native Image onError method will execute on Image when image not found on server or unexpectedly something wrong goes with the connection. Using that you can display text as below,
import React, { Component } from "react";
import { Text, View, Image, StyleSheet } from "react-native";
export default class Example extends Component {
state = {
isLoadingImage: true,
isImageFailed: false
};
onErrorLoadingImage = () => {
this.setState({
isLoadingImage: false,
isImageFailed: true
});
};
render() {
return (
<View style={styles.container}>
{!this.state.isImageFailed ? (
<Image
source={{
uri:
"https://reactnativecode.com/wp-content/uploads/2017/10/Guitar.jpg"
}}
style={styles.imageStyle}
onError={this.onErrorLoadingImage}
/>
) : (
<View style={styles.container}>
<Text>Error loading image</Text>
</View>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 10,
justifyContent: "center",
alignItems: "center"
},
imageStyle: {
resizeMode: "center",
width: "50%",
height: "50%"
}
});
Unfortunately, there is no other way to display text just like HTML alt=" Error loading image "
Hope this will help you.

React Native - Fit image to screen

I just want to fit an image of any size to the phone screen, so it just stays there as a background. I have the same issue with a logo I'm trying to put in the footer, I can't get it to fit in the it's view container.
I've tried many solutions I found in similar questions, using resizeMode and many width/height values, but nothing seems to work. My image is always displayed the same way.
Code for the image component:
import React from 'react';
import { View, Image } from 'react-native';
const Workspace = (props) => {
return (
<View
style = {styles.workspaceStyle}>
<Image
source={props.img}
resizeMode = 'contain'/>
{props.children}
</View>
);
};
const styles = {
workspaceStyle: {
flex: 1
}
}
export default Workspace;
My app.js render and style code:
render() {
return (
<View style = {{flex: 1}}>
<Workspace
img={require('./images/quarto.png')}/>
<ScrollView>
<Header>
<HeaderItem img={require('./images/camera.png')}/>
<HeaderItem img={require('./images/camera.png')}/>
<HeaderItem img={require('./images/camera.png')}/>
<HeaderItem img={require('./images/camera.png')}/>
</Header>
</ScrollView>
<ScrollView style = {{flexDirection: 'row'}}>
{this.sideMenuShow()}
</ScrollView>
<Footer>
<View style = {styles.logoContainerStyle}>
<Image
style = {styles.logoStyle}
source = {require('./images/magicalStage.png')}
resizeMethod = "scale"
/>
</View>
<Text style = {{color: 'white', marginTop: 5, marginBottom: 2}}>teste, teste, teste, teste</Text>
</Footer>
</View>
);
}
}
const styles = {
logoContainerStyle: {
marginRight: 5,
marginLeft: 5,
marginTop: 2,
marginBottom: 3,
width: "20%"
},
logoStyle: {
paddingLeft: 2,
paddingRight: 2
}
}
Thanks in advance!
EDIT:
In app.js, your outer view need to use width and height of the screen:
width: Dimensions.get('window').width,
height: Dimensions.get('window').height
Next, in Workspace: use stretch instead of contain ( same for your footer, add resizeMode )
resizeMode: 'stretch',
I do mine like this:
BackgroundImageStyle.js
import { StyleSheet } from 'react-native'
export default StyleSheet.create({
container: {
flex: 1,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0
},
image: {
flex: 1,
resizeMode: 'cover',
}
})
BacgroundImage.js
import React, { Component } from 'react'
import { View, Image } from 'react-native'
import styles from './BackgroundImageStyle'
export default class BackgroundImage extends Component {
render() {
return (
<View style={styles.container} >
<Image
style={styles.image}
source={this.props.source}
/>
</View>
)
}
}
then you can use it like
<BackgroundImage source={your_image}/>
I hope everything is clear, the trick is to set position absolute and then top, left, bottom, and right to 0
You can use the ImageBackground component from react-native
https://facebook.github.io/react-native/docs/imagebackground
return (
<ImageBackground source={...} style={{width: '100%', height: '100%'}}>
<Text>Inside</Text>
</ImageBackground>
);

React-Native | ScrollView right to left

I've got a simple ScrollView:
<ScrollView
style={$style.category_container}
horizontal={true}
showsHorizontalScrollIndicator={false}
automaticallyAdjustContentInsets={true}
>
<Item title={'1'} />
<Item title={'2'} />
</ScrollView>
Item is a component that loads several thumbnails. My application is planned for both LTR and RTL users, so there is a change in directions for RTL.
The problem is when I'm using the RTL interface - the ScrollView is still moving from left to right, and I can't see all my thumbnails.
How can I solve it?
If someone will run into this in the future:
There isn't any 'built-in' property that will set ScrollView's direction to RTL at the moment.
However That's what worked for me:
set flexDirection: 'row-reverse' to ScrollView's style, which will order your items from right to left.
use onContentSizeChange to init list's scroll on the right side.
Here's an example:
scrollListToStart(contentWidth, contentHeight) {
if (I18nManager.isRTL) {
this.scrollView.scrollTo({x: contentWidth});
}
}
render() {
let containerStyle = I18nManager.isRTL ? styles.RTLContainer : styles.LTRContainer;
return (
<ScrollView
ref={ref => this.scrollView = ref}
onContentSizeChange={this.scrollListToStart.bind(this)}
horizontal={true}
style={[styles.buttonsContainer, containerStyle]}>
{this.renderButtons()}
</ScrollView>
)
}
const styles = StyleSheet.create({
RTLContainer: {
flexDirection: 'row-reverse'
},
LTRContainer: {
flexDirection: 'row'
}
})
you can use this way
i did this and worked for me
This solution has 2 rounds
1-first make this style for your scrollView : style={{scaleX:-1}}
2-second make this style for each of your childs in scrollView : style={{scaleX:-1}}
For Example
<ScrollView
horizontal={true}
contentContainerStyle={{height: 65}}
style={{scaleX:-1}}
showsHorizontalScrollIndicator={false}>
{
data.sports.map((data,index) => {
return(
<View key={index}
style={{width:150,height:55,backgroundColor:'yellow', marginHorizontal:4,scaleX:-1}}/>
)
})
}
</ScrollView>
As you can see my scrollview has scaleX = -1 style
Also all of my childs in scrollView has scaleX = -1 style
as scaleX is deprecated in views you can use transform:[{rotateY:'180deg'}] instead
Just in case someone has a similar problem to mine. I was doing a horizontal ScrollView with user images and needed the images to appear from right to left. Thank you Mr-Ash & Sergey Serduk for getting me there.
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={{
alignSelf: "center",
borderRadius: 100,
transform: [{ scaleX: -1 }],
}}>
{users.map((user, i) => {
return (
<View key={i} style={[{ transform: [{ scaleX: -1 }], zIndex: 100 - i }]}>
<UserImage leftMargin={-27} />
</View>
);
})}
<View style={{ marginRight: 27 }} />
</ScrollView>
In my case a proper solution is wrapping a <ScrollView/> component with <View/> see below:
<View style={{direction: 'rtl'}}><ScrollView ... /></View>
Direction can be set as rtl, ltr, inherit, initial, revert or unset
The best possible solution for RTL ScrollView is the combination of scrolling it to the end and flex-direction: 'row-reverse'
const scrollRef = useRef<any>();
const scrollToEnd = () => {
scrollRef.current.scrollToEnd({ animated: false });
};
<ScrollView
contentContainerStyle={{
flexDirection: "row-reverse"
}}
onContentSizeChange={scrollToEnd}
ref={scrollRefOne}
horizontal
showsHorizontalScrollIndicator={false}
>
...
</ScrollView>

Resources