React Native: Open link on image click - react-native

I have an array arr of dictionary whose elements go like:
{url: 'https://stackoverflow.com/', picUrl: 'link to pic', ...}
I want to open the i-th link when the i-th image is clicked. My code goes as:
arr = this.example[key];
var arrayLength = arr.length;
for(var i = 0; i < arrayLength; i++) {
console.log(arr[i]);
console.log(arr[i].url);
var x = arr[i].url;
views.push(
<View>
<ImageBackground
source={{uri: arr[i].picUrl}}
style={{ width: '100%',aspectRatio: 1, height: undefined }}>
<TouchableOpacity
onPress={() => Linking.openURL(x)}
// or onPress={() => Linking.openURL(arr[i].url)}
style={{flex: 1}}>
</TouchableOpacity>
</ImageBackground>
.....
When I click the image, I get this error:
undefined is not an object (evaluating 'arr[i].url')
I then verified that arr[i] etc. aren't undefined by:
console.log(arr[i])
console.log(arr[i].url)
and got correct values ('https://stackoverflow.com/' for this example).
When I hardcode the value to 'https://stackoverflow.com/', everything seems to work fine, which means there is an issue with the line Linking.openURL(arr[i].url) only. What exactly? I don't know :(
I've been stuck at this issue for quite some time, saw a few posts related to this, but nothing help. Can someone help me resolve this issue? Thanks...
Update:
I changed onPress={() => Linking.openURL(arr[i].url)} to:
onPress={() => Alert.alert(arr[i])}
and I got a completely blank alert!
Then, I did:
var x = arr[i].url
and changed the earlier line to:
onPress={() => Linking.openURL(x)}
Now, for all of the images, the link is set to arr[length-1].url, that is, its equal to the value of the url of the very last image in the array!

You can first check if app can handle this URL using canOpenURL
<TouchableOpacity
onPress={() => Linking.canOpenURL(arr[i].url)
.then((supported) => {
if (!supported) {
console.log("Can't handle url: " + arr[i].url);
} else {
return Linking.openURL(arr[i].url);
}
})
.catch((err) => console.error('An error occurred', err));
}
style={{flex: 1}}>
<ImageBackground
source={{uri: arr[i].picUrl}}
style={{ width: '100%',aspectRatio: 1, height: undefined }}>
</ImageBackground>
</TouchableOpacity>
Update
Instead of for loop you can use map,
let views = arr.map(data => <View>
<ImageBackground
source={{uri: data.picUrl}}
style={{ width: '100%',aspectRatio: 1, height: undefined }}>
<TouchableOpacity
onPress={() => Linking.openURL(data.url)}
style={{flex: 1}}>
</TouchableOpacity>
</ImageBackground>
</View>
)

Related

ReactNative; How to rerender component flipcard after update state

I use flipcard(show front - back) to show value but when I change value to show the new one,Flipcard don't back to the front. It still on same page(but value changed)
Example
data1:Front is "A" ,Back is "B"
data2:Front is "X" ,Back is "Y"
I flip A >> B ,Now on flipcard show "B" after that,I update value. Flipcard show "Y" it's not show "X",I have to flip again to "X" . But, I want to show the front of flipcard every new data. How can I do it?
Nextword() {
this.setState({ count: this.state.count + 1 });
}
render() {
return <View>
<View>
<CardFlip ref={(card) => this.card = card} >
<TouchableOpacity onPress={() => this.card.flip()}>
<Text>{this.state.info[this.state.count].item1}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.card.flip()} >
<Text>{this.state.info[this.state.count].item2}</Text>
</TouchableOpacity>
</CardFlip>
</View >
<View>
<Button title="PRESS1" onPress={this.Nextword.bind(this)}></Button>
</View>
According to the documentation example of react-native-card-flip I just give a style like this to CardFlip component:
style={{
width: 320,
height: 470,
}}
and fix the issue and card flipped.
below the entire code I try:
<View>
<CardFlip
style={{
width: 320,
height: 470,
}}
ref={(card) => (this.card = card)}>
<TouchableOpacity
onPress={() => this.card.flip()}
style={{height: 100, width: 100, backgroundColor: 'green'}}>
<Text>{this.state.info[this.state.count].item1}</Text>
</TouchableOpacity>
<TouchableOpacity
style={{height: 100, width: 100, backgroundColor: 'green'}}
onPress={() => this.card.flip()}>
<Text>{this.state.info[this.state.count].item2}</Text>
</TouchableOpacity>
</CardFlip>
</View>
see the documentation example here: https://github.com/lhandel/react-native-card-flip/blob/master/Example/App.js

Fetching nested data, undefined is not an object

I'm trying to fetch data from API, but I'm only able to fetch the highest level ones. When I'm trying to access ones nested under categories, I get an error: undefined is not an object (evaluating 'this.state.data.order.name' ).
From what I've read it might be an issue with state but I'm new to react-native and I am not sure how to fix it.
This is the API structure
render(){
const { data } = this.state;
return(
<ScrollView style={styles.containerxd}>
<TouchableOpacity style={styles.textStyle}>
<Image
source={require('./images/burger.png')}
style={styles.ImageIconStyle} />
</TouchableOpacity>
<View style={styles.white}>
<View style={{flex:1, alignItems:'center', justifyContent:'center'}}>
<View style={styles.tabHeader}><Text style={styles.textHeader}>Scientific name</Text></View>
<View style={styles.tabContent}><Text style={styles.textContent}>{this.state.data.scientific_name}</Text></View>
<View style={styles.tabHeader}><Text style={styles.textHeader}>Common name</Text></View>
<View style={styles.tabContent}><Text style={styles.textContent}>{this.state.data.common_name}</Text></View>
<View style={styles.tabHeader}><Text style={styles.textHeader}>Moisture use</Text></View>
<View style={styles.tabContent}><Text style={styles.textContent}>{this.state.data.order.name}</Text></View>
Scientific name and common name show up just fine, but every data level lower renders error.
You need to validate your data.When order is undefined, doing order.name will break your app. change
<View style={styles.tabContent}><Text style={styles.textContent}>{this.state.data.order.name}</Text></View>
to
const { data } = this.state;
const name = data && data.order && data.order.name || '';
// rest of the code here
<View style={styles.tabContent}><Text style={styles.textContent}>{name}</Text></View>
NOTE
Always validate your data. Don't assume that you'll always get the right data. When working with objects always validate them, as doing data.name, can break your app, if data is null or undefined. for example, given the following object.
const animal = {};
doing
// throws an error, Cannot read property 'toLowerCase' of undefined
console.log(animal.name.toLowerCase())
to prevent that from happening, we need to check if the propery exists, like the following.
// checks if the name property exists console name, else assign a console log 'Lion'
console.log(animal.name && animal.name.toLowerCase() || 'Lion')
Second option
add a loader, display Loading... text when fetching data from api, once the request finish set loader to false and display your data.
fetchData = async () => {
const res = await fetch(...)
...
this.setState({ isLoading: false, data: response.data });
}
render() {
return (
<ScrollView style={styles.containerxd}>
<TouchableOpacity style={styles.textStyle}>
<Image
source={require('./images/burger.png')}
style={styles.ImageIconStyle}
/>
</TouchableOpacity>
{this.state.isLoading ? (
<Text>Loading...</Text>
) : (
<View style={styles.white}>
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}}
>
<View style={styles.tabHeader}>
<Text style={styles.textHeader}>Scientific name</Text>
</View>
<View style={styles.tabContent}>
<Text style={styles.textContent}>
{this.state.data.scientific_name}
</Text>
</View>
<View style={styles.tabHeader}>
<Text style={styles.textHeader}>Common name</Text>
</View>
<View style={styles.tabContent}>
<Text style={styles.textContent}>
{this.state.data.common_name}
</Text>
</View>
<View style={styles.tabHeader}>
<Text style={styles.textHeader}>Moisture use</Text>
</View>
<View style={styles.tabContent}>
<Text style={styles.textContent}>
{this.state.data.order.name}
</Text>
</View>
</View>
</View>
)}
</ScrollView>
);
}

I want my app to not show articles or give them low priority to those that have already been seen

I have a basic article app like inshorts i am storing all the articles in database which i fetch on the opening and display them in card format.Now I want to implement that when a card id viewed it should get low priority and render at the end next time the app is opened on that mobile.
I have no clue how to implement this.
This is how i am currently rendering it
renderArtciles=()=>{
let len=this.state.dataSource.length;
return this.state.dataSource.map((item,i)=>{
this.state.id=item._id;
this.state.priority=item.priority;
this.state.views=item.views;
if (i == this.state.currentIndex-1)
{
return(
<Animated.View key={item._id} {...this.state.panResponder.panHandlers} style={this.state.swiped_pan.getLayout()}>
< View style={{ flex: 1,position:'absolute',height:height,width:width,backgroundColor:'white'}}>
< View style={styles.Imagebody}>
<Image source={{ uri:item.img.data }} style={styles.image} />
</View>
<View style={styles.inner}>
<Text>{item.body} i==={i}{this.state.currentIndex} </Text>
</View>
</View>
</Animated.View>
)
}
else if (i < this.state.currentIndex)
{
return null
}
if (i == this.state.currentIndex)
{
return(
<Animated.View key={item._id} {...this.state.panResponder.panHandlers} style={this.state.pan.getLayout()}>
< View style={{ flex: 1,position:'absolute',height:height,width:width,backgroundColor:'white'}}>
< View style={styles.Imagebody}>
<Image source={{ uri:item.img.data }} style={styles.image} />
</View>
<View style={styles.inner}>
<Text>{item.body} i==={i}{this.state.currentIndex} </Text>
</View>
</View>
</Animated.View>
)
}
else{
return(
<Animated.View key={item._id} >
< View style={{ flex: 1,position:'absolute',height:height,width:width,backgroundColor:'white'}}>
< View style={styles.Imagebody}>
<Image source={{ uri:item.img.data }} style={styles.image} />
</View>
<View style={styles.inner}>
<Text>{item.body} i==={i}{this.state.currentIndex} </Text>
</View>
</View>
</Animated.View>
)
}
}
).reverse()
}
You could make use of AsyncStorage to store which items have been viewed (and how many times) in a JSON object which you can increment every time an item is viewed, and then retrieve again when the app is opened (and store in some state variable). You can then work out your ordering/priority logic based on the number of views.
To store the items you would do something like this:
_storeData = async () => {
try {
var itemsJson = {items: [
{item_id: 'foo', view_count: 10},
{item_id: 'bar', view_count: 5}
]}
await AsyncStorage.setItem('ItemViews', JSON.stringify(itemsJson));
} catch (error) {
// Error saving data
}
};
And to retrieve the items on app open you would do something like this:
_retrieveData = async () => {
try {
const items = await AsyncStorage.getItem('ItemViews');
// Rest of your code
} catch (error) {
// Error retrieving data
}
};

React Native insert variable

Trying to figure out the syntax for inserting a variable in a function.
var SlidesArray = ['Slide0', 'Slide1'];
var Slides = SlidesArray[Math.floor(Math.random() * SlidesArray.length)];
<Text>{Slides}</Text>
<TouchableHighlight style={styles.button} onPress={() => { this.props.navigation.navigate('{Slides}'); }}>
<Text style={{ color: '#FFFFFF', fontSize: 18 }}>Get New Activity {Slides}</Text>
</TouchableHighlight>
When this renders, I see the value of {Slides} on the screen.
How to do I get that to work inside of
<TouchableHighlight style={styles.button} onPress={() => { this.props.navigation.navigate('{Slides}'); }}>
The goal here is to navigate to a random View from an array of Views.
onPress={() => { this.props.navigation.navigate({Slides}); }} did not work.
Try remove brackets and single quotes on both sides of Slides.
this.props.navigation.navigate(Slides);

Some images not showing up, linking not working in react native

EDIT: The image issue has been resolved, but still not sure about Linking.
Okay so I'm having two weird questions. And apologies ahead of time for the code.
First thing's first, some images will simply not display even though they're valid. When running this in my IOS simulator, the very first image will not display. But some images always work.
The second thing, and let me know if this should be two separate questions, is linking to an external site. It doesn't appear to be able to do the Linking.open in IOS. So I wondered what is the easiest way, through linking or otherwise, of simply opening an external URL in both android and IOS?
Thanks a lot!
openUrl(url) {
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.open(url);
} else {
console.log('nope :: ' + url);
}
}).catch(err => console.error('An error occurred', err));
// browser.open(url);
},
renderImage(event, index) {
if (this.state.showBox && this.state.boxIndex == index) {
return (
<View>
<TouchableHighlight onPress={()=>this._clickImage(event, index)}>
<Image source={{ uri: event.image }} style={[styles.image, this.calculatedSize(), this.getImageStyles(event.featured), { height: 100 }]} />
</TouchableHighlight>
<View style={{ flexDirection:'row', padding: 15 }}>
<Text style={styles.price}>{event.price}</Text>
<Text style={styles.time}>{event.time}</Text>
<TouchableHighlight onPress={()=>this.openUrl(event.website)}>
<Text style={styles.btn}>Website</Text>
</TouchableHighlight>
</View>
{renderif(event.venue)(
<TouchableHighlight onPress={()=>this.openUrl(event.venue)}>
<Text style={styles.btn}>Venue</Text>
</TouchableHighlight>
)}
</View>
)
} else {
return (
<View>
<TouchableHighlight onPress={()=>this._clickImage(event, index)}>
<Image source={{ uri: event.image }} style={[styles.image, this.calculatedSize(), this.getImageStyles(event.featured)]} />
</TouchableHighlight>
</View>
)
}
},
That's because some of your images are trying to load image from http connection.IOS apps require you to use https for images.
For example in this
{
title: 'test',
image: 'http://www.piedmontpark.org/images/bird_pine_warbler_330.jpg',
featured: true,
category: 'Music',
price: '$8.00',
time: '7:00 PM-11:00 PM',
venue: '',
website: 'http://google.com'
}
Your 'image' is trying to load a jpg from http.
Check this out on how to configure your info.plist to accept http