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' }}
/>
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 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,
}
});
I would like to add a link to my app that opens the phone's native camera app? Is this possible?
I'm aware that react-native-camera exists but from the docs it seems like it only supports accessing the camera for the purpose of creating your own camera interface inside your app. I would rather just use the camera app already on the phone.
Thank you
Use react-native-image-picker, you can access phone native's camera.
[EDIT]
An example Code
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Image,
Button
} from 'react-native';
import ImagePicker from "react-native-image-picker";
export default class App extends Component {
state = {
pickedImage: null
}
reset = () => {
this.setState({
pickedImage: null
});
}
pickImageHandler = () => {
ImagePicker.showImagePicker({title: "Pick an Image",
maxWidth: 800, maxHeight: 600}, res => {
if (res.didCancel) {
console.log("User cancelled!");
} else if (res.error) {
console.log("Error", res.error);
} else {
this.setState({
pickedImage: { uri: res.uri }
});
}
});
}
resetHandler = () =>{
this.reset();
}
render() {
return (
<View style={styles.container}>
<Text style={styles.textStyle}>Pick Image From Camera and Gallery</Text>
<View style={styles.placeholder}>
<Image source={this.state.pickedImage} style={styles.previewImage} />
</View>
<View style={styles.button}>
<Button title="Pick Image" onPress={this.pickImageHandler} />
<Button title="Reset" onPress={this.resetHandler} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
alignItems:"center"
},
textStyle: {
fontWeight:"bold",
fontSize:30,
textAlign:"center",
color:"red",
marginTop:10
},
placeholder: {
borderWidth: 1,
borderColor: "black",
backgroundColor: "#eee",
width: "70%",
height: 280,
marginTop:50,
},
button: {
width: "80%",
marginTop:20,
flexDirection:"row",
justifyContent: "space-around"
},
previewImage: {
width: "100%",
height: "100%"
}
});
}
Source: react-native-image-picker
I've been looking for examples of injectJavaScript. On GitHub I found a few which, I guess for testing, do:
injectJavaScript={()=>'alert("Injected JS ")'}
But I can't make it work. I thought that perhaps I had to wait for the WebView to be loaded, but still no luck.
Here my test:
export default class App extends React.Component {
constructor( props ){
super( props );
this.state = {
loaded: false
};
}
webviewDidLoad(){
this.setState({loaded: true});
}
render() {
return (
<WebView
source={ webview }
injectJavaScript={ this.state.loaded ? ()=>'alert("Injected JS")' : null }
onLoadEnd={ this.webviewDidLoad.bind(this) }
/>
);
}
}
Is the only way to communicate to the WebView through strings and props? No way to communicate with WebView methods passing native javascript objects?
Thanks for your help!
import React, { Component } from 'react';
import {
Text,
View,
StyleSheet,
TouchableHighlight,
WebView,
} from 'react-native';
let jsCode = `
document.querySelector('#myContent').style.backgroundColor = 'blue';
`;
export default class App extends Component {
render() {
return (
<View style={localStyles.viroContainer}>
<WebView
source={{ html: "<h1 id='myContent'>Hello</h1>" }}
style={{ flex: 1 }}
ref={webview => {this.myWebview = webview;}}
injectedJavaScript={jsCode}
javaScriptEnabled={true}
/>
<TouchableHighlight
style={localStyles.overlayButton}
onPress={this.sendMessageToWebView2}
underlayColor="transparent">
<Text>Send message to WebView</Text>
</TouchableHighlight>
</View>
);
}
sendMessageToWebView2 = () => {
console.log(this.myWebview);
console.log(this);
this.myWebview.injectJavaScript(`
(function () {
document.querySelector('body').style.backgroundColor = 'orange';
})();
`);
};
}
var localStyles = StyleSheet.create({
viroContainer: {
flex: 1,
},
overlayButton: {
position: 'absolute',
bottom: 0,
left: 110,
height: 50,
width: 150,
paddingTop: 30,
paddingBottom: 30,
marginTop: 10,
marginBottom: 10,
backgroundColor: '#f0a0aa',
borderRadius: 10,
borderWidth: 2,
borderColor: '#000',
},
});
I am setting a listener in my application and using force update whenever it is broadcasted but it gives error forceUpdate cant be called on unmounted component. How can I check if a component is mounted now that the isMounted() function is deprecated.
'use strict';
var React = require('react-native');
import ExpAndroid from './ExpAndroid';
var {
AppRegistry,
Image,
ListView,
TouchableHighlight,
StyleSheet,
Text,
View,
Component,
AsyncStorage,
Navigator,
DeviceEventEmitter
} = React;
var rowID;
var img=require('./resource/ic_pause_white.png');
class Example1 extends Component{
constructor(props) {
super(props);
this.state = {
};
}
componentWillMount(){
rowID = this.props.rowIdentity;
console.log("rowID "+rowID);
}
componentDidMount(){
console.log('component mounted')
this.start();
DeviceEventEmitter.addListener('playMusicStatus', (data)=> {
if(data.playMusic==true){
img=require('./resource/ic_pause_white.png');
rowID++;
this.setState(this.state);
ExpAndroid.someMethod1("someurl);
}
});
}
componentWillUnmount(){
console.log('componentwill unmounted')
}
start() {
var url = "some url";
ToastAndroid.prepareToPlay(url,true);
}
render() {
return (
<Image source={require('./resource/album_back.png')} style={styles.background}>
<Image
source={{uri:this.state.trackDetails[rowID].thumnail_loc}}
style={styles.thumbnail}
/>
<View style={styles.flowRow}>
<Text
style={styles.titles}
>text1 + {rowID}: </Text>
<Text
style={styles.titles}
>{this.state.details[rowID].text1}</Text>
</View>
<View style={styles.flowRow}>
<Text
style={styles.titles}
>text2 : </Text>
<Text
style={styles.titles}
>{this.state.details[rowID].text2}</Text>
</View>
<View style={styles.flowRow}>
<Text
style={styles.titles}
>Text3 : </Text>
<Text
style={styles.titles}
>{this.state.Details[rowID].Text3}</Text>
</View>
<View style={styles.flowRow}>
<Text
style={styles.titles}
>Text4 : </Text>
<Text
style={styles.titles}
>{this.state.details[rowID].Text4}</Text>
</View>
</Image>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
},
background: {
flex: 1,
width: null,
height: null,
},
flowRow : {
flexDirection :'row',
},
flowRowPlay : {
flexDirection :'row',
alignSelf:'center',
},
backgroundImage: {
flex: 1,
resizeMode: 'cover', // or 'stretch'
},
thumbnail: {
width: 100,
height: 120,
alignSelf:'center',
margin :30
},
controls: {
width: 30,
height: 30,
margin:20
},
titles: {
fontSize: 15,
margin:20,
color: 'white',
},
timings: {
fontSize: 12,
margin:5,
color: 'white',
},
});
module.exports = Example1;
You can handle this yourself in your component:
componentDidMount() {
this._mounted = true;
}
componentWillUnmount() {
this._mounted = false;
}
Then you can check the value of this._mounted in your listener.
Please note that using forceUpdate() should be avoided https://facebook.github.io/react/docs/component-api.html#forceupdate
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your component "pure" and your application much simpler and more efficient.
What I did was changing the callback in componentWillMount.
let asyncCallback;
componentDidMount(){
asyncCallback = res=> this.setState({data: res});
asyncTask(asyncCallback);
}
componentWillUnmount(){
asyncCallback = ()=> console.log("AsyncCallback called but component has unmounted");
}
Using ReactUpdateQueue, you can avoid managing your own isMounted state.
const ReactUpdateQueue = require('ReactUpdateQueue');
// Pass the ref to your component.
if (ReactUpdateQueue.isMounted(view)) {
// Your component is mounted!
}