I'm using react-native-image-crop-picker to upload multiple images. I have used the following code to do that
ImagePicker.openPicker({
multiple: true
}).then(images => {
this.setState({
avatarSource: images,
});
});
When selected images it will receive this array
[{"height": 1280, "mime": "image/jpeg", "modificationDate": "1572089089000","path": "file:///data/user/0/com.carup/cache/react-native-image-crop-picker/image-ed1c260f-ee73-4ec0-932b-167e9771d24f.jpg",
"size": 199376, "width": 960}]
I have tried to show selected images (in android) with the following code
<Image style={{marginTop:10,height:150, resizeMode:'contain'}} source={{uri:avatarSource.path}}/>
but it will not be showing the images. How can I show selected images?
Here is the answer, used react-native NativeModules.ImageCropPicker instead of react-native-image-crop-picker
import React, {Component} from 'react';
import {
View, Text, StyleSheet, ScrollView, Alert,
Image, TouchableOpacity, NativeModules, Dimensions, StatusBar, SafeAreaView
} from 'react-native';
import {CarColors} from "../assets/Colors";
var commonStyles = require('../assets/style');
var ImagePicker = NativeModules.ImageCropPicker;
export default class App extends Component {
constructor() {
super();
this.state = {
image: null,
images: null
};
}
cleanupImages() {
ImagePicker.clean().then(() => {
// console.log('removed tmp images from tmp directory');
alert('Temporary images history cleared')
}).catch(e => {
alert(e);
});
}
pickMultiple() {
ImagePicker.openPicker({
multiple: true,
waitAnimationEnd: false,
includeExif: true,
forceJpg: true,
}).then(images => {
this.setState({
image: null,
images: images.map(i => {
console.log('received image', i);
return {uri: i.path, width: i.width, height: i.height, mime: i.mime};
})
});
}).catch(e => alert(e));
}
scaledHeight(oldW, oldH, newW) {
return (oldH / oldW) * newW;
}
renderImage(image) {
return <Image style={{width: 200, height: 200, resizeMode: 'contain'}} source={image}/>
}
renderAsset(image) {
if (image.mime && image.mime.toLowerCase().indexOf('video/') !== -1) {
return this.renderVideo(image);
}
return this.renderImage(image);
}
render() {
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
<StatusBar
backgroundColor={CarColors.primary}
barStyle="light-content"/>
<TouchableOpacity onPress={this.pickMultiple.bind(this)} style={commonStyles.button}>
<Text style={commonStyles.text}>Select Images</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.cleanupImages.bind(this)} style={commonStyles.button}>
<Text style={commonStyles.text}>Clean History</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.imgContainer}>
{this.state.image ? this.renderAsset(this.state.image) : null}
{this.state.images ? this.state.images.map(i => <View style={styles.imgView}
key={i.uri}>{this.renderAsset(i)}</View>) : null}
{
this.state.images &&
<TouchableOpacity onPress={this.cleanupImages.bind(this)} style={commonStyles.bottomBtn}>
<Text style={commonStyles.text}>Upload</Text>
</TouchableOpacity>
}
</ScrollView>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: CarColors.white,
},
imgContainer: {
marginVertical: 20
},
button: {
backgroundColor: 'blue',
marginBottom: 10,
},
text: {
color: 'white',
fontSize: 20,
textAlign: 'center'
},
title: {
fontWeight: 'bold',
fontSize: 22
},
safeArea: {
marginTop: 20
},
dateContainer: {
flexDirection: 'row',
},
imgView: {
width: '50%',
marginVertical: 10,
}
});
Related
I am looking for assistance on how to replace a default profile picture with the one a user would select from their media library.
I have managed to create an onPress function that allows the user to select the image from their media library. The image is returned and displayed also in the prescribed layout.
My problem is that I cannot see the default profile picture, but I can see and click on a pencil icon to prompt the image-picker for iOS and Android as I am using Expo.
Here is my custom component code:
import React, { useState, useEffect } from "react";
import {
StyleSheet,
View,
Text,
Image,
TouchableOpacity,
useWindowDimensions,
Platform,
} from "react-native";
import { Controller } from "react-hook-form";
import * as ImagePicker from "expo-image-picker";
import { Ionicons } from "#expo/vector-icons";
//import dependencies
import { COLORS, SIZES, images } from "../constants";
const CustomImagePicker = ({ control, name, rules = {} }) => {
const { height } = useWindowDimensions();
const [hasGalleryPermission, setHasGalleryPermission] = useState("false");
const [profilePicture, setProfilePicture] = useState(name);
useEffect(() => {
async () => {
const galleryStatus =
await ImagePicker.requestMediaLibraryPermissionsAsync();
setHasGalleryPermission(galleryStatus.status === "granted");
};
}, []);
const pickImage = async () => {
let chosenImage = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
console.log(JSON.stringify(chosenImage));
if (!chosenImage.cancelled) {
setProfilePicture(chosenImage.uri);
}
};
if (hasGalleryPermission === false) {
return <Text>❌ No access to Internal Storage</Text>;
}
return (
<Controller
name={name}
control={control}
rules={rules}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<>
<View
style={[
styles.container,
{ borderColor: error ? COLORS.red : COLORS.gray },
]}
>
<TouchableOpacity
style={styles.touchPicture}
onPress={() => pickImage()}
>
<Ionicons name="pencil-outline" size={24} color={COLORS.white} />
</TouchableOpacity>
<Image
onChange={onChange}
value={value}
source={{
uri: profilePicture ? profilePicture : images.defaultRounded,
}}
style={[
styles.logo,
styles.profileImage,
{ height: height * 0.19 },
]}
resizeMode={Platform.OS === "android" ? "contain" : "cover"}
/>
</View>
{error && (
<Text
style={{
color: COLORS.red,
alignSelf: "stretch",
fontSize: SIZES.body5,
padding: SIZES.padding - 22,
marginTop: 15,
marginHorizontal: SIZES.padding * 3,
}}
>
{error.message || "❌ Oops, something went wrong!"}
</Text>
)}
</>
)}
/>
);
};
export default CustomImagePicker;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
touchPicture: {
zIndex: 10,
marginBottom: -50,
marginLeft: 100,
resizeMode: "contain",
},
logo: {
width: Platform.OS == "android" ? 155 : 164,
maxWidth: 300,
maxHeight: 200,
},
profileImage: {
marginTop: SIZES.padding * 2,
borderRadius: 100,
},
});
I'm using NetInfo to get the connection but some devices android crash with that
LOG:
Error Tag: Mini App Bundle Message: null Stack: android.net.ConnectivityManager$TooManyRequestsException at android.net.ConnectivityManager.convertServiceException(ConnectivityManager.java:3687) at android.net.ConnectivityManager.sendRequestForNetwork(ConnectivityManager.java:3924) at android.net.ConnectivityManager.registerDefaultNetworkCallback(ConnectivityManager.java:4334) at android.net.ConnectivityManager.registerDefaultNetworkCallback(ConnectivityManager.java:4311) at com.reactnativecommunity.netinfo.NetworkCallbackConnectivityReceiver.register(NetworkCallbackConnectivityReceiver.java:42) at com.reactnativecommunity.netinfo.NetInfoModule.initialize(NetInfoModule.java:38) at com.facebook.react.bridge.ModuleHolder.doInitialize(ModuleHolder.java:222) at com.facebook.react.bridge.ModuleHolder.markInitializable(ModuleHolder..java:97) at com.facebook.react.bridge.NativeModuleRegistry.notifyJSInstanceInitialized(NativeModuleRegistry.java:102) at com.facebook.react.bridge.CatalystInstanceImpl$2.run(CatalystInstanceImpl.java:441) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:26) at android.os.Looper.loop(Looper.java:237) at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:225) at java.lang.Thread.run(Thread.java:919)
In my code:
import MaxApiManager from '#common/MaxApiManager';
import { throttle } from 'lodash';
import React, { useEffect, useState } from 'react';
import { View, TouchableOpacity, Image, StyleSheet, Text } from 'react-native';
import ChatImages from 'src/screens/Chat/img';
import { mapNameWithLocalContact } from 'src/screens/Chat/utils/ChatUtils';
import { Avatar, Skeleton } from '#momo-platform/component-kits';
import NetInfo from '#react-native-community/netinfo';
export function HeaderConversation({
relationShip,
relationshipText,
friendInfo = {},
goToProfile = () => {},
}) {
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
NetInfo?.fetch()?.then(state => {
setIsConnected(state.isConnected);
});
}, []);
return (
<View style={styles.headerLeft}>
<TouchableOpacity
style={styles.buttonBack}
onPress={throttle(() => MaxApiManager.dismiss(), 1000, {
leading: true,
trailing: false,
})}
>
<Image source={ChatImages.ic_back} style={styles.icBack} />
</TouchableOpacity>
{relationShip || isConnected === false ? (
<TouchableOpacity onPress={goToProfile} style={styles.info}>
<Avatar
size="small"
name={friendInfo?.name}
source={{ uri: friendInfo?.avatar }}
/>
<View style={{ marginLeft: 12 }}>
<Text style={styles.txtRoomName}>
{mapNameWithLocalContact({
phone: friendInfo?.phone,
name: friendInfo?.name,
})}
</Text>
{relationShip && <Text>{relationshipText}</Text>}
</View>
</TouchableOpacity>
) : (
<View style={{ paddingTop: 12 }}>
<Skeleton.Custom
left={<Skeleton.Media size={35} />}
style={styles.skeletonItem}
>
<Skeleton.Line style={styles.width_1_9} />
<Skeleton.Line style={styles.width_1_10} />
</Skeleton.Custom>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
headerLeft: {
paddingLeft: 8,
flexDirection: 'row',
alignItems: 'center',
},
buttonBack: { padding: 8, marginRight: 4 },
width_1_9: { width: 150, height: 16 },
width_1_10: { width: 100, height: 12, marginTop: -6 },
skeletonItem: {
marginVertical: 5,
},
info: {
flexDirection: 'row',
alignItems: 'center',
},
txtRoomName: { fontSize: 16, fontWeight: 'bold' },
icBack: { width: 24, height: 24 },
});
AnalyticsModule.js
const AnalyticModule = {
netInfoSub: null,
initialize(deviceInfo) {
UserProfile.getInstance().getData().then((profile) => {
Storage.getItem("", (ipAddress) => {
StorageCache.get("").then(location => {
if (!this.netInfoSub) {
this.netInfoSub = NetInfo.addEventListener((state) => {
let netInfo = getNetworkInfo(state);
this.TRACKING_NETWORK_INFO = JSON.stringify(netInfo);
})
}
})
})
})
},
}
More infor:
#react-native-community/netinfo: ^5.9.9
react-native: 0.61.5
The easiest way to obtain network status information in your functional component is by using useNetInfo hook
import {useNetInfo} from '#react-native-community/netinfo';
const YourComponent = () => {
const netInfo = useNetInfo();
return (
<View>
<Text>Type: {netInfo.type}</Text>
<Text>Is Connected? {netInfo.isConnected.toString()}</Text>
</View>
);
};
for more details on property object: netinfo Docs
We have an eventListener for NetInfo and the mistake is we don't remove this event and with android, NetworkCallbacks have a limit(Maybe is 100 enter link description here)
native i'm trying to swipe to delete a Rdvdetail ( second code )
but when i delete it ,it doesn't disappear from scrollView i have to reopen the page to make it disappear but backside
it works perfectly well i don't how it gonna works to make it disappear
is there any way i could make it disappear immediately from the scrollView ?
by auto reloading the scrollview or filer any help ?
import React, { Component } from "react";
import { ScrollView } from "react-native";
import axios from "axios";
import RdvDetail from "./RdvDetail";
import { CardSection } from "./utilities/CardSection";
import { Spinner } from "./utilities/Spinner";
import Swipeout from "react-native-swipeout";
class Event extends Component {
constructor(props) {
super(props);
this.state = {
Rdvs: [],
rowIndex: null,
refreshing: false
};
}
componentWillMount() {
this.fetchdata();
}
getInitialState = () => {
return {
scrollEnabled: true
};
};
_allowScroll = scrollEnabled => {
this.setState({ scrollEnabled: scrollEnabled });
};
fetchdata = () => {
axios
.get("http://localhost:3000/api/5cc92f1b8929820fecdecda3/mesRdv")
.then(response => this.setState({ Rdvs: response.data }));
};
deleteRdv = id_rdv => {
axios
.delete("http://localhost:3000/api/rdv/" + id_rdv)
.then(response => {
if (response.status === 200) {
console.log(response.data);
}
})
.catch(error => {
console.log(error.response.data.message);
if (error.response.status === 400) {
}
});
};
renderRDV() {
if (this.state.Rdvs.length < 1) {
console.log("here");
return (
<CardSection>
<Spinner size="large" />
</CardSection>
);
} else {
return this.state.Rdvs.map(Rdv => (
<Swipeout
right={[
{
text: "Delete",
backgroundColor: "red",
color: "white",
onPress: () => this.deleteRdv(Rdv._id),
autoClose: true
}
]}
rowIndex={Rdv._id}
sectionId={0}
autoClose={true}
key={Rdv._id}
scroll={event => this._allowScroll(event)}
>
<RdvDetail key={Rdv._id} Rdv={Rdv} />
</Swipeout>
));
}
}
render() {
return (
<ScrollView scrollEnabled={this.state.scrollEnabled}>
{this.renderRDV()}
</ScrollView>
);
}
}
export default Event;
import React, { Component } from "react";
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";
const AlbumDetail = props => {
state = {
rowIndex: null,
monthNames: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
]
};
return (
<View style={styles.container}>
<View style={styles.eventBox}>
<View style={styles.eventDate}>
<Text style={styles.eventDay}>{props.Rdv.day + " "}</Text>
<Text style={styles.eventMonth}>
{this.state.monthNames[props.Rdv.month - 1]}
</Text>
</View>
<View style={styles.eventContent}>
<Text style={styles.eventTime}>{props.Rdv.time}</Text>
<Text style={styles.userName}>{props.Rdv.doctor}</Text>
<Text style={styles.description}>Rdv note</Text>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: "#DCDCDC"
},
eventList: {
marginTop: 20
},
eventBox: {
padding: 10,
marginTop: 5,
marginBottom: 5,
flexDirection: "row"
},
eventDate: {
flexDirection: "column"
},
eventDay: {
fontSize: 30,
color: "#0099FF",
fontWeight: "600"
},
eventMonth: {
fontSize: 16,
color: "#0099FF",
fontWeight: "600"
},
eventContent: {
flex: 1,
flexDirection: "column",
alignItems: "flex-start",
marginLeft: 10,
backgroundColor: "#FFFFFF",
padding: 10,
borderRadius: 10
},
description: {
fontSize: 15,
color: "#646464"
},
eventTime: {
fontSize: 18,
color: "#151515"
},
userName: {
fontSize: 16,
color: "#151515"
},
test: {
borderRadius: 20
}
});
export default AlbumDetail;
You could update the Rdvs list in your state state after each delete which would cause the list to re-render. Like this:
deleteRdv = id_rdv => {
axios
.delete("http://localhost:3000/api/rdv/" + id_rdv)
.then(response => {
if (response.status === 200) {
console.log(response.data);
}
this.fetchdata(); // Add this line to fetch new list
})
.catch(error => {
console.log(error.response.data.message);
if (error.response.status === 400) {
}
});
};
"objects are not valid as a react child (found: object with keys {date, events}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of View."
So I have kind of a cascade of method calls. I'm retrieving dates with events inside of those. It feels like I'm doing this correctly, but am getting the above error. I've tried setting createFragment on places, but still getting the error. Here's the code:
import React, { Component } from 'react';
import {
AppRegistry,
Text,
View,
ScrollView,
RefreshControl,
StyleSheet,
Dimensions,
TextInput,
Linking,
TouchableNativeFeedback
} from 'react-native';
var _ = require('lodash');
var {width, height} = Dimensions.get('window');
var renderif = require('render-if');
var createFragment = require('react-addons-create-fragment');
var IMAGES_PER_ROW = 1
class FunInATL extends Component {
constructor(props) {
super(props);
this.state = {
currentScreenWidth: width,
currentScreenHeight: height,
dates: [],
boxIndex: 0,
showBox: false,
refreshing: false
};
}
handleRotation(event) {
if (!this.state) {
return;
}
var layout = event.nativeEvent.layout
this.state({currentScreenWidth: layout.width, currentScreenHeight: layout.height })
}
calculatedSize() {
var size = this.state.currentScreenWidth / IMAGES_PER_ROW
return {width: size}
}
renderRow(events) {
return events.map((events, i) => {
return (
<Image key={i} style={[this.getImageStyles(), styles.image, this.calculatedSize() ]} source={{uri: event.image}} />
)
})
}
openUrl(url) {
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
} else {
console.log('nope :: ' + url);
}
}).catch(err => console.error('An error occurred', err));
}
getImageStyles(featured, category) {
let options = {
borderColor: 'gold',
borderWidth: featured ? 1 : 0
}
if (!category) {
options.height = featured ? 250 : 125
}
return options;
}
_clickImage(event, index) {
if (event.title) {
let new_val = !this.state.showBox
this.setState({
dates: this.state.dates,
showBox: new_val,
boxIndex: new_val ? index : 0
});
}
}
componentDidMount() {
this.state = {
dates: [],
boxIndex: 0,
showBox: false,
refreshing: false
};
this.getApiData();
Linking.addEventListener('url', this.handleUrl);
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleUrl);
}
getApiData() {
var _this = this;
return fetch('https://www.funinatl.com/mobile2.php?v1')
.then(function(response) {
return response.json()
})
.then((responseJson) => {
var dates = createFragment(responseJson.events)
return;
let _this = this;
date.events.map((event, i) => (
date.events[i] = event
))
var datesData = [];
dates.map((date, i) => (
datesData.push({
date: i,
events: createFragment(date.events)
})
))
_this.setState({
dates: createFragment(datesData),
boxIndex: 0,
showBox: false
})
console.error(this.state);
})
.catch((error) => {
console.error(error);
})
.done();
}
renderDates() {
return this.state.dates.map((date) =>
(
<View>
<Text style={styles.dateHeader}>{ date.date }</Text>
<View>
{this.renderEvents(date.events)}
</View>
</View>
))
}
renderImage(event, index) {
if (this.state.showBox && this.state.boxIndex == index) {
return (
<View>
<TouchableNativeFeedback onPress={()=>this._clickImage(event, index)}>
<Image source={{ uri: event.image }} style={[styles.image, this.calculatedSize(), this.getImageStyles(event.featured), { height: 100 }]} />
</TouchableNativeFeedback>
<View style={{ flexDirection:'row', padding: 15 }}>
<Text style={styles.price}>{event.price}</Text>
<Text style={styles.time}>{event.time}</Text>
<TouchableNativeFeedback onPress={()=>this.openUrl(event.website)}>
<Text style={styles.btn}>Website</Text>
</TouchableNativeFeedback>
</View>
{renderif(event.venue)(
<TouchableNativeFeedback onPress={()=>this.openUrl(event.venue)}>
<Text style={styles.btn}>Venue</Text>
</TouchableNativeFeedback>
)}
</View>
)
} else {
return (
<View>
<TouchableNativeFeedback onPress={()=>this._clickImage(event, index)}>
<Image source={{ uri: event.image }} style={[styles.image, this.calculatedSize(), this.getImageStyles(event.featured)]} />
</TouchableNativeFeedback>
</View>
)
}
}
renderEvents(events) {
return events.map((event, i) =>
(
<View>
<Text style={[styles.eventCategory, this.getImageStyles(event.featured, true)]}>{event.category}</Text>
<View>
{this.renderImage(event, i)}
</View>
<Text style={[styles.eventTitle, this.getImageStyles(event.featured, true)]}>{event.title}</Text>
</View>
));
}
_onRefresh() {
this.setState({refreshing: true});
fetchData().then(() => {
this.setState({refreshing: false});
});
}
render() {
return (
<ScrollView onLayout={this.handleRotation} contentContainerStyle={styles.scrollView} refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh.bind(this)}
tintColor="#ff0000"
title="Loading..."
titleColor="#00ff00"
colors={['#ff0000', '#00ff00', '#0000ff']}
progressBackgroundColor="#ffff00"
/>
}>
<Text style={styles.header}>FunInATL</Text>
{this.renderDates()}
</ScrollView>
)
}
}
var styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
textAlign: 'center',
padding: 10
},
header: {
fontSize: 30,
fontWeight: 'bold',
padding: 20,
textAlign: 'center',
backgroundColor: '#000',
color: '#fff'
},
dateHeader: {
fontSize: 20,
fontWeight: 'bold',
padding: 20,
textAlign: 'left',
color: '#fff',
backgroundColor: '#283593'
},
eventCategory: {
backgroundColor: '#03a9f4',
textAlign: 'center',
color: '#ffffff',
padding: 3
},
eventTitle: {
borderTopWidth: 0,
textAlign: 'center',
fontWeight: 'bold',
padding: 3,
fontSize: 18,
},
image: {
},
btn: {
backgroundColor: 'green',
padding: 10,
color: '#fff',
textAlign: 'center',
flex: 1
},
price: {
marginLeft: 10,
fontSize: 16,
flex: 1
},
time: {
marginRight: 10,
fontSize: 16,
flex: 1,
width: 35
}
});
AppRegistry.registerComponent('FunInATL', () => FunInATL);
Thanks!
EDIT: Updated code per the map suggestion, still not working. complaining about {events} only now.
EDIT 2: Updated with FULL code.
The component's render helpers, such as renderDates(), are returning _.each(...). _.each() returns its first argument so this is why you are receiving the error.
To illustrate:
const myObject = { a: 1 };
_.each(myObject) === myObject // true
I recommend you use Array.prototype.map() instead:
return this.state.dates.map((date) => (
<View>...</View>
));
If you use arrow functions like I did in the example above, there's no need to save a reference to this. this in the body of the function passed to map() will be bound to the instance of the component. You can then call other helper methods such as getImageStyles() like this.getImageStyles(...).
This is not related to your original question but the getApiData() method will not work. You can replace the function in the chain that handles responseJson with something like:
(responseJson) => {
this.setState({
dates: Object.entries(responseJson.events).map(([date, { events }]) => ({
date,
events,
})),
boxIndex: 0,
showBox: false,
});
}
You also need to to remove the this.state = {...} in componentDidMount(). Notice the warning in the docs that indicates you should "NEVER mutate this.state directly".
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?
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) }
/>
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
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,
}}
/>
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' }}
/>