Like button on React Native? I can't make it dynamically update - javascript

class PostButtons extends Component {
constructor() {
super();
this.state = {
liked: true,
};
}
likeToggled() {
this.setState({
liked: !this.state.liked,
});
}
render() {
const heartIconColor = () => {
if (this.state.liked === true) {
return "red";
} else if (this.state.liked === false) {
return "black";
}
};
<View>
<TouchableOpacity
onPress={() => {
this.likeToggled;
}}
>
<Image
style={{
height: 37,
width: 37,
marginVertical: 395,
marginHorizontal: 10,
justifyContent: "flex-start",
position: "absolute",
tintColor: heartIconColor(),
}}
source={require("../Img/Heart3.png")}
></Image>
</TouchableOpacity>
</View>
I need it so when someone clicks the like button (initial state is black) it turns red then when they click it again it goes back to black. Currently i need to change this.state.liked to true and false to change the colours. I am not that advanced so an answer thats not verbose would be greatly appreciated. Thanks

you weren't calling your likeToggled function. you were just accessing its definition which doesn't run the code inside your likeToggled function calling it like likeToggled() does
<TouchableOpacity onPress={() => { this.likeToggled();}} >

Related

TypeError: undefined is not an object (evaluating '_this.state.data)

The error occurs when I try to close the modal window. on the "Close" button. The error says that _this.state.data [imageKey] .urls' is not an object, but I pass the string.
I am receiving JSON from a custom API endpoint. I was able to iterate over the JSON and output some data, but the problem is with the string reference to the image. I am getting the error:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TouchableWithoutFeedback,
Dimensions,
Modal,
ScrollView,
} from 'react-native';
import ImageElement from './component/ImageElement';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
modalVisible: false,
data: [],
};
}
componentDidMount() {
fetch('https://api.unsplash.com/photos/?client_id=cf49c08b444ff4cb9e4d126b7e9f7513ba1ee58de7906e4360afc1a33d1bf4c0')
.then((response) => response.json())
.then((data) => {
this.setState({
data: data,
});
});
}
// getImage() {
// return this.state.modalImage
// }
setModalVisible = (visible, imageKey) => {
this.setState({ modalImage: this.state.data[imageKey].urls.raw });
this.setState({ modalVisible: visible });
};
render() {
let images = this.state.data.map((val, key) => {
return (
<TouchableWithoutFeedback
key={key}
onPress={() => {
this.setModalVisible(true, key);
}}
>
<View style={styles.imagewrap}>
<ImageElement
author={{ url: val.user.profile_image.small }} // фото автора
imgsource={{ url: val.urls.small }} // работа автора
authorNameProp={val.user.name} // имя автора
></ImageElement>
</View>
</TouchableWithoutFeedback>
);
});
return (
<ScrollView>
<View style={styles.container}>
<Modal
style={styles.modal}
animationType={'fade'}
transparent={true}
visible={this.state.modalVisible}
onRequestClose={() => {}}
>
<View style={styles.modal}>
<Text
style={styles.text}
onPress={() => {
this.setModalVisible(false);
}}
>
Close
</Text>
<ImageElement imgsource={{ url: this.state.modalImage }}></ImageElement>
</View>
</Modal>
{images}
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
backgroundColor: 'grey',
overflow: 'scroll',
},
imagewrap: {
margin: 5,
padding: 2,
height: Dimensions.get('window').height / 2 - 60,
width: Dimensions.get('window').width / 2 - 10,
},
modal: {
flex: 1,
padding: 10,
backgroundColor: 'rgba(0,0,0, 0.9)',
},
text: {
color: '#fff',
zIndex: 20,
position: 'absolute',
top: 50,
right: 30,
fontSize: 20,
},
user: {
color: '#fff',
zIndex: 20,
position: 'absolute',
top: 20,
left: 10,
},
});
AppRegistry.registerComponent('ImageGallery', () => ImageGallery);
Issue
Seems you missed passing an index in your close handler this.setModalVisible(false), imageKey is undefined, and thus this.state.data[undefined] is undefined and throws error when you access deeper into the nested properties.
Solution
Conditionally access the data value based on the visible value. Clear out the modalImage state when closing the modal.
setModalVisible = (visible, imageKey) => {
this.setState({
modalImage: visible ? this.state.data[imageKey].urls.raw : null,
modalVisible: visible,
});
}
Alternatively you can just use Optional Chaining to do null checks for you.
setModalVisible = (visible, imageKey) => {
this.setState({
modalImage: this.state.data[imageKey]?.urls?.raw,
modalVisible: visible,
});
}

Cannot display Array in Flatlist format

Long story short, I'm trying to create a list of video-games. I have an autocomplete library that matches a game title with what I write in my input box.
I added a button that onPress triggers a function that pushes what I have in 'query'( which is what I am writing in my input box) into an array called myGamesArray
Then I'm trying to display myGamesArray in list form using Flatlist. But for some reason it is not showing up.
My code is a little messy because the solution I found to place a button next to my input box was by changing the flex direction to 'row'
I added a button right below the FlatList just to check if it would display underneath the inputbox (which it does) but for some reason the flatlist does not
Here's all my App.js
/*This is an example of AutoComplete Input/ AutoSuggestion Input*/
import React, { Component } from 'react';
//import react in our code.
import { StyleSheet, Text, TouchableOpacity, View, Image, Alert, FlatList } from 'react-native';
//import all the components we are going to use.
import Autocomplete from 'react-native-autocomplete-input';
import { Button, List, Container, ListItem, Header } from 'native-base';
//import Autocomplete component
//Demo base API to get the data for the Autocomplete suggestion
class App extends Component {
constructor(props) {
super(props);
//Initialization of state
//games will contain the array of suggestion
//query will have the input from the autocomplete input
this.state = {
myGamesArray: [],
games: [],
query: '',
};
}
componentDidMount() {
//First method to be called after components mount
//fetch the data from the server for the suggestion
fetch('https://api.rawg.io/api/games?page=1&platforms=18', {
"method": "GET",
"headers": {
"x-rapidapi-host": "rawg-video-games-database.p.rapidapi.com",
"x-rapidapi-key": "495a18eab9msh50938d62f12fc40p1a3b83jsnac8ffeb4469f"
}
})
.then(res => res.json())
.then(json => {
const { results: games } = json;
this.setState({ games });
//setting the data in the games state
});
}
findGame(query) {
//method called everytime when we change the value of the input
if (query === '') {
//if the query is null then return blank
return [];
}
const { games } = this.state;
//making a case insensitive regular expression to get similar value from the game json
const regex = new RegExp(`${query.trim()}`, 'i');
//return the filtered game array according the query from the input
return games.filter(game => game.name.search(regex) >= 0);
}
AddItemsToArray=()=>{
//Adding Items To Array.
this.state.myGamesArray.push( this.state.query.toString() )
// Showing the complete Array on Screen Using Alert (just to check if it's infact inside the array)
Alert.alert(this.state.myGamesArray.toString());
}
render() {
const { query } = this.state;
const games = this.findGame(query);
const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim();
return (
<View>
<View style={styles.container}>
<Autocomplete
style={styles.autocompleteContainer1}
autoCapitalize="none"
autoCorrect={false}
//data to show in suggestion
data={games.length === 1 && comp(query, games[0].name) ? [] : games}
//default value if you want to set something in input
defaultValue={query}
/*onchange of the text changing the state of the query which will trigger
the findGame method to show the suggestions*/
onChangeText={text => this.setState({ query: text })}
placeholder="Selecione os jogos que voce quer receber!"
renderItem={({ item }) => (
//you can change the view you want to show in suggestion from here
//I GET ERROR WHEN TRYING TO ERASE (PS4) IN TEXT BOX ***NEED TO CHECK THIS
<View style={styles.iconContainer} >
<TouchableOpacity onPress={() => this.setState({ query: item.name})}
style={styles.itemContainer} >
<View>
<Image
style={styles.profilepic}
source={{uri: item.background_image}}
/>
</View>
<Text style={styles.itemText}>
{item.name} (PS4)
</Text>
</TouchableOpacity>
</View>
)}
/>
<Button
style={styles.addButton}
icon='plus'
title="Click Here To Add Value To Array"
onPress={() => this.AddItemsToArray()}
/>
<View style={styles.descriptionContainer}>
{games.length > 0 ? (
//Text inside input box
<Text style={styles.infoText}>{this.state.query}</Text>
) : (
<Text style={styles.infoText}>{this.state.query}</Text>
)
}
</View>
</View>
<View style={styles.container2}>
<Button
full></Button>
<FlatList
data={this.state.myGamesArray}
renderItem={({ item }) => (
<ListItem>{item}</ListItem>
)}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
flex: 1,
flexDirection:'row',
padding: 16,
marginTop: 40,
justifyContent: 'center',
},
container2: {
backgroundColor: '#fff',
flex: 1,
marginTop: 100,
alignItems: 'center',
justifyContent: 'center',
},
autocompleteContainer1: {
borderWidth: 1,
backgroundColor: "#fff",
borderColor: '#7843FF',
height: 50,
width:300
},
itemContainer: {
flex:1,
flexDirection: 'row',
borderWidth:0.5,
borderColor: '#7843FF',
paddingVertical: 5,
alignItems: 'center',
paddingRight: 60
},
addButton: {
width:50,
height: 50,
borderColor: '#7843FF',
borderWidth: 0.5,
backgroundColor: '#fff'
},
descriptionContainer: {
flex: 1,
justifyContent: 'center',
borderRadius: 10,
color: '#fff',
borderColor: '#7843FF'
},
itemText: {
fontSize: 15,
marginLeft: 10,
marginRight:30,
marginVertical:10,
color: '#000',
textAlign: 'left',
justifyContent: 'center'
},
infoText: {
textAlign: 'center',
color: '#000',
},
profilepic: {
flex: 3,
height: 60,
width: 60,
marginLeft:10,
borderRadius: 100,
},
});
export default App;
Issue
this.state.myGamesArray.push(this.state.query.toString()) is a state mutation. You need to actually do a state update and return a new array object reference.
Solution
Use a functional state update to access the previous state object, and shallow copy the old array into a new array reference and append the new query value to the end.
Also, logging or otherwise trying to reference the updated state right after enqueueing the update won't work like that, state is const and updated between render cycles so it'll enclose the current state.
AddItemsToArray = () => {
//Adding Items To Array.
this.setState(prevState => {
const { myGamesArray, query } = prevState;
return {
myGamesArray: [...myGamesArray, query.toString()],
};
},
// Use setState callback to alert with the updated state
() => {
// Showing the complete Array on Screen Using Alert (just to check if it's in fact inside the array)
Alert.alert(this.state.myGamesArray.toString());
},
);
}

Change Fragment by Clicking to Checkbox in React Native

I recently joined to React Native project team. Due to time constraint, there is no time to learn JavaScript in depth thats why I directly continued the project. My question is how to change fragment by click to the checkbox. So that, when I click to checkbox it changes to another fragment and when I click to uncheck checkbox it goes back to the previous fragment. I found this React Native - CheckBox unable to uncheck the "checked" box
but it was a little bit harder for me to understand.
Here what I did:
export default class App extends React.Component {
constructor(props) {
super(props);
//state to manage the screen visible at a time
this.state = { val: 1 };
}
renderElement() {
//You can add N number of Views here in if-else condition
if (this.state.val === 1) {
//Return the FirstScreen as a child to set in Parent View
return <FirstScreen />;
} else if (this.state.val === 2) {
//Return the SecondScreen as a child to set in Parent View
return <SecondScreen />;
} else {
//Return the ThirdScreen as a child to set in Parent View
return <ThirdScreen />;
}
}
...
<TouchableOpacity
style={styles.button}
onPress={() => this.setState({ val: 1 })}>
<Text style={{ color: '#ffffff' }}>1st View</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => this.setState({ val: 2 })}>
<Text style={{ color: '#ffffff' }}>2nd View</Text>
</TouchableOpacity>
<CheckBox
value = { this.state.check }
onChange={() => this.setState({ val: 2 })/>
//This is where my fragment changes
<View style={{ backgroundColor: '#ffffff' }}>
{this.renderElement()}
</View>
Currently, I am able to go to 2nd fragment. But I can't go back to the first fragment and uncheck the checkbox.
export default class App extends React.Component {
constructor(props) {
super(props);
//state to manage the screen visible at a time
this.state = { val: 1, checked: false };
}
//Toggle state on checkbox change
toggleFragment() {
if(this.state.val == 1) {
this.setState({ val: 2, checked: !this.state.checked });
} else {
this.setState({ val: 1, checked: !this.state.checked});
}
}
renderElement() {
//You can add N number of Views here in if-else condition
if (this.state.val === 1) {
//Return the FirstScreen as a child to set in Parent View
return <FirstScreen />;
} else if (this.state.val === 2) {
//Return the SecondScreen as a child to set in Parent View
return <SecondScreen />;
} else {
//Return the ThirdScreen as a child to set in Parent View
return <ThirdScreen />;
}
}
...
<TouchableOpacity
style={styles.button}
onPress={() => this.setState({ val: 1 })}>
<Text style={{ color: '#ffffff' }}>1st View</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => this.setState({ val: 2 })}>
<Text style={{ color: '#ffffff' }}>2nd View</Text>
</TouchableOpacity>
<CheckBox
value = 'checked'
checked={this.state.checked == true}
onChange={() => this.toggleFragment()/>
//This is where my fragment changes
<View style={{ backgroundColor: '#ffffff' }}>
{this.renderElement()}
</View>
export default class App extends React.Component {
constructor(props) {
super(props);
//state to manage the screen visible at a time
this.state = { check: true };
}
renderElement() {
if (this.state.check) {
return <FirstScreen />;
} else {
return <SecondScreen />;
}
}
...
<TouchableOpacity
style={styles.button}
onPress={() => this.setState({ val: 1 })}>
<Text style={{ color: '#ffffff' }}>1st View</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => this.setState({ val: 2 })}>
<Text style={{ color: '#ffffff' }}>2nd View</Text>
</TouchableOpacity>
<CheckBox
value = { this.state.check }
onValueChange={(newValue) => this.setState({check:newValue})/>
//This is where my fragment changes
<View style={{ backgroundColor: '#ffffff' }}>
{this.renderElement()}
</View>
Thank you Raju and Waheed Akhter. By combining your answers I found a solution. Here is a solution:
constructor(props) {
super(props);
//state to manage the screen visible at a time
this.state = { val: 1, checked: true };
}
//Toggle state on checkbox change
toggleFragment() {
if(this.state.val == 1) {
this.setState({ val: 2});
} else {
this.setState({ val: 1});
}
}
.....
<CheckBox
value = { this.state.check }
onChange={() => this.toggleFragment()}
onValueChange={(newValue) => this.setState({check:newValue})}/>

react-native re-run function on navigating back

I'm using react-navigation to navigate between screens, now in my "cases.js" I have a fetch method that gets the cases from the API when the user click on one it loads another page with the clicked case to either accept or deny the case itself.
Either way the user will be redirected to the "cases.js" page if the user accepts the case I no longer need the case to be available on the "cases.js" page list.
Here is my cases.js file
export default class PendingCases extends Component {
constructor(props) {
super(props);
this.state = { cases : [], user : '', refreshing : false }
}
componentDidMount = async () => {
await this._getUser();
await this._getCases();
}
_getUser = async () => {
let user = await AsyncStorage.getItem('user');
await this.setState({ user : JSON.parse(user) })
}
_getCases = async () => {
try {
await this.setState({ refreshing : true })
let pendingCases = await fetch('http://192.168.1.101:1337/user/cases?status=pending&userType=patient&id=' + this.state.user.id);
let response = await pendingCases.json();
await this.setState({ cases : response.cases });
await this.setState({ refreshing : false })
} catch (err) {
console.log(err);
}
}
render() {
let casesArray = this.state.cases.map((singleCase, index) => {
return (
<View key={ index }>
<TouchableOpacity onPress={ () => this.props.navigation.navigate('SelectedCaseDetails') } style={ styles.caseButton }>
<View>
<View style={{ marginBottom: 10, marginTop: 5 }}>
<LinearTextGradient locations={[0, 1]} start={{x: 0, y: 0}} end={{x: 1, y: 0}} colors={['#a4d294', '#3ac6f3']} style={{ fontWeight: 'bold' }}>
{singleCase.doctor.name}
</LinearTextGradient>
<Text style={{ position: 'absolute', right: 0, fontWeight: '600', color: 'black'}}> 00231 </Text>
</View>
<View style={{ marginBottom: 10 }}>
<Text> {singleCase.additionalInformation} </Text>
</View>
<View style={{ marginBottom: 10, flex : 1, flexDirection: 'row'}}>
<Image style={ styles.statusImage } source={require('../images/status.png')} />
<Text style={{ marginRight : 30, color : 'rgba(0, 0, 0, .8)', flex : 8}}>
Status <Text style={{ color : 'rgb(240, 151, 166)', marginLeft : 60 }}> {singleCase.status} </Text> </Text>
<Text style={{ position: 'absolute', right: 0, fontWeight: '300', color: 'grey'}}> { parsedDate } </Text>
</View>
</View>
</TouchableOpacity>
</View>
)
});
return (
<View>
{ casesArray }
</View>
)
}
}
How can I re-run the this._getCases() function when I redirect the user from the one-level deep page ?
I'm redirecting the user the same way as navigating to any other screen
this.props.navigation.navigate('CasesScreen')
If your component is already mounted, you can use react-navigation's listeners to check whether the component is focused or not.
You'll need
didFocus: the screen focused (if there was a transition, the transition completed)
componentDidMount () {
this._getUser();
this._onFocusListener = this.props.navigation.addListener('didFocus', (payload) => {
this._getCases();
});
}
and remove it once the component gets unmounted.
componentWillUnmount () {
this._onFocusListener.remove()
}

How can I remove Alert prompts programmatically?

On a button click, I start a process that takes sometime and once it gets finished I navigate to a specified screen.
While under process, I Alert a "loading" prompt which according to the docs: "By default, the only button will be an 'OK' button". And once the process is done, I alert again that the data is ready.
Problem is that I get two prompts above each other, and that they need user's click to be removed.
I would like to remove the first prompt before displaying the second (and maybe set a timer for the second to then remove it as well).
How can I remove Alert prompts programmatically?
A basic workaround in order to achieve what you're asking for would be to create a separate component that will act as the alert instead.
The component that I've written accepts two props. text and visible.
Inside your screen you can add it as so:
import React from 'react'
[....]
export default class Screen extends React.Component {
state = {
visible: true,
text: "Loading... Please wait"
}
drawAlert() {
setTimeout(() => {
this.setState({text: "Will dismiss in 1 second"}, () => {
setTimeout(() => {
this.setState({visible: false})
}, 1000);
})
}, 4000); // fake API request
return (
<Alert text={this.state.text} visible={this.state.visible} />
)
}
render() {
return(
<Alert text={this.state.text} visible={this.state.visible} />
)
}
}
This is the alert component
import React, { Component } from 'react'
import { View, TouchableOpacity, Text } from 'react-native'
export default class Alert extends Component {
state = {
text: this.props.text,
visible: this.props.visible
}
componentWillReceiveProps(nextProps) {
this.setState({text: nextProps.text, visible: nextProps.visible})
}
drawBox() {
if (this.state.visible) {
return(
<View style={styles.container}>
<View style={styles.boxContainer}>
<View style={styles.textContainer}>
<Text style={styles.text}>{this.state.text}</Text>
</View>
<TouchableOpacity onPress={this.props.onPress} style={styles.buttonContainer}>
<Text style={styles.buttonText}>OK</Text>
</TouchableOpacity>
</View>
</View>
)
}
}
render() {
return(
<View style={styles.container}>
{this.drawBox()}
</View>
)
}
}
const styles = {
wrapper: {
flex: 1,
},
container: {
zIndex: 99999,
position: "absolute",
backgroundColor: "rgba(0, 0, 0, 0.1)",
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
},
boxContainer: {
backgroundColor: "#FFF",
borderRadius: 2,
padding: 10,
width: 300,
borderColor: "rgba(0,0,0,.1)",
borderBottomWidth: 0,
shadowOffset: { width: 0, height: 2 },
elevation: 1,
padding: 20
},
textContainer: {
marginBottom: 20
},
text: {
color: "#424242",
fontFamily: "Roboto",
fontSize: 18
},
buttonContainer: {
alignItems: 'flex-start'
},
buttonText: {
color: "#009688"
}
}
Hope it helps.

Categories

Resources