Conditional rendering based on state not working on state change - react-native

I have a value in my state that changes based on a Switch. Depending on the value of that state item I want to change the styling of my button. I can see the state change, but the color doesn't change.
This seems straightforward as shown in this documentation: https://reactjs.org/docs/conditional-rendering.html
I actually have one conditional rendering working in the same statement, but it is referencing redux state instead of component state.
I've included the relevant aspects of my code below and tried to strip out the unnecessary stuff.
Still kind of a bit long.
/* eslint-disable prettier/prettier */
import React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
Dimensions,
StyleSheet,
TextInput,
Switch,
ActivityIndicator,
} from 'react-native';
import { connect } from 'react-redux';
// Actions
import { createUser } from '../../actions/user-actions';
// Getting dims
const { width: WIDTH } = Dimensions.get('window');
// Styling
const styles = StyleSheet.create({
containerStyle: {
flex: 1,
backgroundColor: 'black',
alignItems: 'center',
justifyContent: 'center',
},
footerContainerStyle: {
justifyContent: 'flex-start',
flex: 3,
width: WIDTH,
paddingHorizontal: 30,
alignItems: 'center',
marginTop: 45,
},
tcStyle: {
flexDirection: 'row',
marginBottom: 20,
},
switchStyle: {
marginRight: 15,
},
buttonStyle: {
width: WIDTH-100,
height: 50,
backgroundColor: '#007AFF',
borderRadius: 4,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 20,
},
disabledButtonStyle: {
width: WIDTH-100,
height: 50,
backgroundColor: '#007AFF',
borderRadius: 4,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 20,
opacity: 0.3,
},
buttonTextStyle: {
color: 'white',
fontSize: 14,
fontWeight: 'bold',
},
linkStyle: {
textDecorationLine: 'underline',
color: 'blue',
},
});
// Component
class Signup extends Component {
constructor(props) {
super(props);
this.state = {
switchValue: false,
email: '',
name: '',
password: '',
passwordConfirm: '',
errorMessage: '',
};
}
componentDidUpdate() {
console.log(this.props.users);
const { navigation } = this.props;
if (this.props.users.user) {
navigation.navigate('Home');
}
}
componentDidMount() {
console.log(this.props);
}
// Helper functions
toggleSwitch = (value) => {
this.setState({switchValue: value});
};
onSignUp = () => {
const { email, name, password, passwordConfirm, switchValue } = this.state;
const { createUser } = this.props;
if (password !== passwordConfirm) {
console.log('Passwords do not match')
this.setState({errorMessage: 'Passwords do not match'});
return;
}
if (!switchValue) {
console.log('You must agree to terms');
this.setState({errorMessage: 'You must agree to terms'});
return;
}
createUser(email, password, name);
}
render() {
// Conditional button rendering
const { email, password, passwordConfirm, name, switchValue } = this.state;
let button;
console.log(switchValue);
if (this.props.users.loading) {
button = (
<TouchableOpacity style={[styles.buttonStyle]} onPress={this.onSignUp}>
<ActivityIndicator size="large" />
</TouchableOpacity>
);
} else if (switchValue) {
button = (
<TouchableOpacity style={[styles.buttonStyle]} onPress={this.onSignUp}>
<Text style={styles.buttonTextStyle}>CREATE AN ACCOUNT</Text>
</TouchableOpacity>
)
} else if (!switchValue) {
button = (
<TouchableOpacity style={[styles.disabledButtonStyle]} onPress={this.onSignUp}>
<Text style={styles.buttonTextStyle}>CREATE AN ACCOUNT</Text>
</TouchableOpacity>
)
}
return (
<View style={styles.containerStyle}>
<View style={styles.footerContainerStyle}>
<View style={styles.tcStyle}>
<Switch
onValueChange = {this.toggleSwitch}
value = {this.state.switchValue}
style = {styles.switchStyle}
trackColor={{true: '#007AFF', false: 'grey'}}
/>
<Text style={{color: 'white', flexWrap: 'wrap', flex: 1}}>I have read & agree to the <Text style={styles.linkStyle}>Terms of Use</Text> and <Text style={styles.linkStyle}>Privacy Policy</Text></Text>
</View>
{button}
<View style={styles.textLinkStyle}>
<Text style={styles.ctaHelpTextStyle}>Have an account?</Text>
<TouchableOpacity>
<Text style={styles.ctaTextStyle}> Sign In</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
}
// state mapping
const mapStateToProps = ({ users }) => ({
users,
});
// Export
export default connect(mapStateToProps, {
createUser,
})(Signup);
If I toggle that switch I am expecting the button component to change to the one with the other styling. That's not happening though.

Actually your state changes correctly, but opacity does not work.
Update:
it seems react native has issues changing TouchableOpacity's opacity.
One solution is removing the opacity from the styles. And wrapping TouchableOpacity component with a View component, giving View an opacity.
You can try like this:
else if (switchValue) {
button = (
<View opacity={0.5}>
<TouchableOpacity style={styles.buttonStyle} onPress={this.onSignUp}>
<Text style={styles.buttonTextStyle}>CREATE AN ACCOUNT true</Text>
</TouchableOpacity>
</View>
);
} else if (!switchValue) {
button = (
<View opacity={0.1}>
<TouchableOpacity
style={styles.disabledButtonStyle}
onPress={this.onSignUp}
>
<Text style={styles.buttonTextStyle}>CREATE AN ACCOUNT false</Text>
</TouchableOpacity>
</View>
);

add return in conditions for example
if (this.props.users.loading) {
return(
<TouchableOpacity style={[styles.buttonStyle]} onPress{this.onSignUp}>
<ActivityIndicator size="large" />
</TouchableOpacity>
);
}

Based on you code, the only difference between both buttons is just the styling.
The way i would approach this is simply make the conditioning inside the styling its self not an if statement on the render method itself, follow along:
Solution
Modify this fragment:
// ... Other code parts
if (this.props.users.loading) {
button = (
<TouchableOpacity style={[styles.buttonStyle]} onPress={this.onSignUp}>
<ActivityIndicator size="large" />
</TouchableOpacity>
);
} else if (switchValue) {
button = (
<TouchableOpacity style={[styles.buttonStyle]} onPress={this.onSignUp}>
<Text style={styles.buttonTextStyle}>CREATE AN ACCOUNT</Text>
</TouchableOpacity>
)
} else if (!switchValue) {
button = (
<TouchableOpacity style={[styles.disabledButtonStyle]} onPress={this.onSignUp}>
<Text style={styles.buttonTextStyle}>CREATE AN ACCOUNT</Text>
</TouchableOpacity>
)
}
to this:
// ... Other code parts
if (this.props.users.loading) {
button = (
<TouchableOpacity style={[styles.buttonStyle]} onPress={this.onSignUp}>
<ActivityIndicator size="large" />
</TouchableOpacity>
);
} else {
button = (
<TouchableOpacity style={switchValue ? styles.buttonStyle : styles.disabledButtonStyle} onPress={this.onSignUp}>
<Text style={styles.buttonTextStyle}>CREATE AN ACCOUNT</Text>
</TouchableOpacity>
)
}
Hope this Helps!

Related

How can I setup a multiple like system in a wishList in react Native

I'm currently working on a whishlist, I've tried two different methods.
The first one works but if I press my like ( heart icon), all the heart get fill.
The second method I can only select one article, not few.
My hook state first :
const [liked, setLiked] = useState(false)
const [counter, setCounter] =useState(-2)
const[likedProduct, setLikedProduct]=useState(false)
My code for the first point :
if(likedProduct){
var colorLike = {color: 'red'}
} else {
var colorLike = {}
}
var ArticleList = articleData.map((article, i) => {
return (<View style={{width: '42%'}}>
<TouchableOpacity
onPress={() => {
onSubmitProduct(productId)
navigation.navigate('Product')
}
}
>
<Image source={ article.img} style={{ height: 250, width: 200} } />
<View style={{ flex: 1, flexDirection: 'row', marginTop: 5, justifyContent: "space-between" }}>
<Text style={{ fontWeight: 'bold' }}>{article.brand}</Text>
<FontAwesome name="heart" size={20} style={colorLike}
onPress={() => setLikedProduct(!likedProduct)
}
/>
</View>`
The code of my second point :
<AntDesign name={liked && i== counter ? "heart":"hearto"} size={20} color="red"
onPress={()=>{
setLiked(!liked)
setCounter(i)
Any idea to get multiple like ( onPress on multiple heart) ?
Thanks
It's relatively simple to add this functionality to the app, below is the behavior of the final sample app:
import React, { useState } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
import { FontAwesome } from '#expo/vector-icons';
const articleData = ['one', 'two', 'three', 'four', 'five'];
export default function App() {
/*
to determine if the list item is liked or not,
we will set up a state "liked", which is an array and holds
the indexes of liked list items.
*/
const [liked, setLiked] = useState([]);
/*
on TouchableOpacity we will use the below function,
it will first see if the index of cliked list item is already present,
if yes then we remove that index from "liked" state array, which is
similar to unlike functionality, and if the index is not present,
then we push that index to the "liked" state.
-----------------------------------------
onPress={() => {
console.log(liked);
if (liked.includes(index)) {
let unlike = liked.filter((elem) => elem !== index);
setLiked(unlike);
} else {
setLiked([...liked, index]);
}
}}
------------------------------------------
then comes the styling of the icon, which is pretty simple,
we just see if the index of that list item is present in the "liked" state,
if yes then it means, that item is liked and we set the color of the icon "red" else "black"
<FontAwesome
name="heart"
size={20}
😏 ➡ style={{ color: liked.includes(index) ? 'red' : 'black' }}
/>
*/
return (
<View style={styles.container}>
{articleData.map((article, index) => (
<TouchableOpacity
onPress={() => {
console.log(liked);
if (liked.includes(index)) {
let unlike = liked.filter((elem) => elem !== index);
setLiked(unlike);
} else {
setLiked([...liked, index]);
}
}}>
<View style={styles.list}>
<Text>{article}</Text>
<FontAwesome
name="heart"
size={20}
style={{ color: liked.includes(index) ? 'red' : 'black' }}
/>
</View>
</TouchableOpacity>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
list: {
padding: 10,
margin: 5,
flexDirection: 'row',
flex: 1,
justifyContent: 'space-between',
backgroundColor: 'white',
},
});
Working App : Expo Snack

React Native TouchableOpacity onPress using index/key of the item in FlatList

We created a FlatList component and a Contact screen in our app. We want to add 'heart' icon to the near of the images in Contact screen. We added heart icon near to all of items. But, if we pressed one of these icons, it changes all of theirs colors to red, not only one of them. We want to change the color of clicked item.
Screenshot of our program:
This is our FlatList component:
import React, { Component } from 'react';
import { View, Text, SafeAreaView, StyleSheet, FlatList, Image, TouchableOpacity,
TouchableWithoutFeedback, TextInput } from 'react-native';
import { Right, Icon } from 'native-base';
import data from '../../data';
export default class FlatListComponent extends Component {
state = {
text: '',
contacts: data,
like: false,
color: 'white',
}
toggleLike=()=>{
this.setState({
like: !this.state.like
})
if(this.state.like){
this.setState({
color: 'red',
})
}else{
this.setState({
color: 'white',
})
}
}
renderContactsItem = ({item, index}) => {
return (
<View style={[styles.itemContainer]}>
<Image
style={styles.avatar}
source={{ uri: item.image }} />
<View style={styles.textContainer}>
<Text style={[styles.name], {color: '#fafafa'}}>{item.first_name}</Text>
<Text style={{ color: '#fafafa' }}>{item.last_name}</Text>
</View>
<Right style={{justifyContent: 'center'}}>
<TouchableWithoutFeedback onPress={this.toggleLike}>
{/*{this.like ? (
<Icon name="heart" type='FontAwesome' style={{paddingRight: 10, fontSize: 30, color: 'red'}} />
) :
( <Icon name="heart" type='FontAwesome' style={{paddingRight: 10, fontSize: 30, color: 'white'}} /> )
}*/}
<Icon name='heart' type='FontAwesome' size={32} style={{color: this.state.color === "white" ? 'white' :'red', paddingRight: 10 }}/>
{/*<Icon name="heart" type='FontAwesome' style={{paddingRight: 10, fontSize: 30, color: this.state.color}} />*/}
</TouchableWithoutFeedback>
</Right>
</View>
);
}
searchFilter = text => {
const newData = data.filter(item => {
const listItems = `${item.first_name.toLowerCase()}`
return listItems.indexOf(text.toLowerCase()) > -1;
});
this.setState({
contacts: newData,
});
};
renderHeader = () => {
const {text} = this.state;
return (
<View style={styles.searchContainer}>
<TextInput
onChangeText = {text => {
this.setState ({
text,
});
this.searchFilter(text);
}}
value={text}
placeholder="Search..."
style={styles.searchInput} />
</View>
)
}
render() {
return (
<FlatList
ListHeaderComponent={this.renderHeader()}
renderItem={this.renderContactsItem}
keyExtractor={item => item.id}
data={this.state.contacts}
/>
);
}
}
const styles = StyleSheet.create({
itemContainer: {
flex: 1,
flexDirection: 'row',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: '#eee'
},
avatar: {
width: 50,
height: 50,
borderRadius: 25,
marginHorizontal: 10,
},
textContainer: {
justifyContent: 'space-around',
},
name: {
fontSize: 16,
},
searchContainer: {
padding: 10
},
searchInput: {
fontSize: 16,
backgroundColor: '#f9f9f9',
padding: 10,
}
});
Our Contact screen is just:
import React from 'react';
import 'SafeAreaView' from 'react-native';
import FlatList from './FlatList';
export default function Contact() {
<SafeAreaView style={{ flex: 1 }}>
<FlatList />
</SafeAreaView>
}
How can we implement this?
I've run into this recently :) One option is to make the renderContactsItem its own component. For example:
const RenderContactsItem = ({item, index}) => {
const [like, setLike] = useState(false);
const [color, setColor] = useState("white");
const toggleLike = () => {
setLike(!like)
if(like) {
setColor("red");
} else {
setColor("white");
}
}
return (
<View style={[styles.itemContainer]}>
<Image
style={styles.avatar}
source={{ uri: item.image }} />
<View style={styles.textContainer}>
<Text style={[styles.name], {color: '#fafafa'}}>{item.first_name}</Text>
<Text style={{ color: '#fafafa' }}>{item.last_name}</Text>
</View>
<Right style={{justifyContent: 'center'}}>
<TouchableWithoutFeedback onPress={toggleLike}>
<Icon name='heart' type='FontAwesome' size={32} style={{color, paddingRight: 10 }}/>
</TouchableWithoutFeedback>
</Right>
</View>
);
}
In this case, each item manages its own state, so setting like doesn't set it for every item.
Another option would be to build an object with "like" states and set the values as the hearts are pressed. For example:
state = {
text: '',
contacts: data,
like: {},
color: 'white', // You don't need this
}
Then, when a heart is pressed, you can send toggleLike the index, and set the state like so:
toggleLike = (index) => {
let newLike = {...this.state.like};
newLike[index] = !Boolean(newLike[index]);
this.setState({
like: newLike,
});
}
And render the color conditionally depending on the index value of the like state, like so:
<Icon name='heart' type='FontAwesome' size={32} style={{color: this.state.like[index] ? 'red' :'white', paddingRight: 10 }}/>

wants to capture image in by using expo camera

i'm trying to capture image from expo camera but when i alert my photo nothing shown up here is complete code of my component
import React from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import { Camera, Permissions } from 'expo';
import {
Container,
Title,
Content,
Header,
Button,
Switch,
Left,
Body,
Right,
List,ListItem,Thumbnail,Footer,FooterTab
} from "native-base";
import {Icon} from 'react-native-elements';
export default class CameraEx extends React.Component {
static navigationOptions = {
header: null
}
state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
};
async componentDidMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === 'granted' });
}
takePicture = async function() {
if (this.camera) {
let photo = await this.camera.takePictureAsync();
alert(photo);
}
}
render() {
const { hasCameraPermission } = this.state;
if (hasCameraPermission === null) {
return <View />;
} else if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
} else {
return (
<Container style={{ flex: 1 }}>
<Header transparent>
<Left>
<Button transparent onPress={() =>this.props.navigation.navigate('Home') }>
<Icon name='arrow-back' />
</Button>
</Left>
</Header>
<Camera style={{ flex: 1 }} type={this.state.type}>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
justifyContent:'space-between',
width:"50%"
}}>
<TouchableOpacity
style={{
//flex: 0.1,
alignSelf: 'flex-end',
alignItems: 'center',
}}
onPress={() => {
this.setState({
type: this.state.type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back,
});
}}>
<Text
style={{ fontSize: 18, marginBottom: 10, color: 'white' }}>
{' '}Flip{' '}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
// flex: 0.1,
alignSelf: 'flex-end',
alignItems: 'center',
}}
onPress={()=>this.takePicture()}
>
<Text
style={{ fontSize: 18, marginBottom: 10, color: 'white' }}>
{' '}Take Picture{' '}
</Text>
</TouchableOpacity>
</View>
</Camera>
</Container>
);
}
}
}
here is a complete code my component please let me know how i can capture image and stored into state i'm beginner ............................................................................................................................................. ....
you cannot show a picture in alert box. you need to pass the source or base64 of image to react native Image component.
import {ImagePicker} from 'expo';
const options = {
base64: true,
allowsEditing: true
};
const data = await ImagePicker.launchCameraAsync(options);
if (data.cancelled !== true) {
this.setState({imageBase64: data.base64});
}
and then you can use like this:
<Image source={`data:image/png;base64, ${this.state.imageBase64}`} />

Implement #mention in TextInput

How can I implement #mention in react native's TextInput?
I've tried this react-native-mention but it is not being maintained anymore. There are so many styling issues and callback issues.
What I want is to display custom view inside TextInput. Something like this.
And after tapping on the list I want to display like this:
So far I am able to achieve:
When I type '#' in TextInput user list appear.
And when I tap on user I get username in TextInput
Code:
renderSuggestionsRow() {
return this.props.stackUsers.map((item, index) => {
return (
<TouchableOpacity key={`index-${index}`} onPress={() => this.onSuggestionTap(item.label)}>
<View style={styles.suggestionsRowContainer}>
<View style={styles.userIconBox}>
<Text style={styles.usernameInitials}>{!!item.label && item.label.substring(0, 2).toUpperCase()}</Text>
</View>
<View style={styles.userDetailsBox}>
<Text style={styles.displayNameText}>{item.label}</Text>
<Text style={styles.usernameText}>#{item.label}</Text>
</View>
</View>
</TouchableOpacity>
)
});
}
onSuggestionTap(username) {
this.setState({
comment: this.state.comment.slice(0, this.state.comment.indexOf('#')) + '#'+username,
active: false
});
}
handleChatText(value) {
if(value.includes('#')) {
if(value.match(/#/g).length > 0) {
this.setState({active: true});
}
} else {
this.setState({active: false});
}
this.setState({comment: value});
}
render() {
const {comments} = this.state;
return (
<View style={styles.container}>
{
this.state.active ?
<View style={{ marginLeft: 20}}>
{this.renderSuggestionsRow()}
</View> : null
}
<View style={{ height: 55}}/>
<View style={styles.inputContainer}>
<TextInput
style={styles.inputChat}
onChangeText={(value) => this.handleChatText(value)}
>
{comment}
</TextInput>
<TouchableOpacity style={styles.inputIcon} onPress={() => this.addComment()}>
<Icon type='FontAwesome' name='send-o' style={{fontSize: 16, color: '#FFF'}}/>
</TouchableOpacity>
</View>
</View>
);
}
One simple solution would be to use react-native-parsed-text.
Here is an example:
import * as React from "react";
import { Text, View, StyleSheet } from 'react-native';
import ParsedText from 'react-native-parsed-text';
const userNameRegEx = new RegExp(/#([\w\d.\-_]+)?/g);
export default class Example extends React.Component {
handleNamePress = (name) => {
alert("Pressed username " + name);
}
render() {
return (
<View style={styles.container}>
<ParsedText
style={styles.text}
parse={
[
{pattern: userNameRegEx, style: styles.username, onPress: this.handleNamePress},
]
}
childrenProps={{allowFontScaling: false}}
>
This is a text with #someone mentioned!
</ParsedText>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
text: {
color: 'black',
fontSize: 15,
},
username: {
color: 'white',
fontWeight: 'bold',
backgroundColor: "purple",
paddingHorizontal: 4,
paddingBottom: 2,
borderRadius: 4,
},
});
However, this library doesn't support rendering custom views. The example above is achieved by just pure styling. If you need a custom view you need to implement something yourself. For a long time, it wasn't possible to render arbitrary components embedded inside a text-components. However, this has changed now afaik and we can do stuff like this:
<Text>Hello I am an example <View style={{ height: 25, width: 25, backgroundColor: "blue"}}></View> with an arbitrary view!</Text>
Check both code examples here: https://snack.expo.io/#hannojg/restless-salsa
One important note: You can render the output of the ParsedText or your own custom component inside the TextInput, like this:
<TextInput
...
>
<ParsedText
...
>
{inputValue}
</ParsedText>
</TextInput>

Adjacent JSX elements must be wrapped in an enclosing tag (React-Native)

How can I solve that, because i can't figure out how to solve. I change the some parts of the code, change the root and add some codes on root,js to try not crash the entire app, but still show me this error. thanks for the help.
import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
export default class Tabs extends Component {
state = {
activeTab: 0
}
render({children} = this.props) {
return (
<div>
<View style={styles.container}>
<View style={styles.tabsContainer}>
{children.map(({ props: { title } }, index) => {
<TouchableOpacity
style={[
// Default style for every tab
styles.tabContainer,
index === this.state.activeTab ? styles.tabContainerActive : []
]}
// Change active tab
onPress={() => this.setState({ activeTab: index }) }
// Required key prop for components generated returned by map iterator
key={index}
>
<Text style={styles.tabText}>
{title}
</Text>
</TouchableOpacity>
<View style={styles.contentContainer}>
{children[this.state.activeTab]}
</View>
</View>
</div>
);
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
tabsContainer: {
flexDirection: 'row',
paddingTop: 30,
},
tabContainer: {
flex: 1,
paddingVertical: 15,
borderBottomWidth: 3,
borderBottomColor: 'transparent',
},
tabContainerActive: {
borderBottomColor: '#FFFFFF',
},
tabText: {
color: '#FFFFFF',
fontWeight: 'bold',
textAlign: 'center',
},
contentContainer: {
flex: 1
}
});
You are missing a closing view tag for the tabs container and you can remove the div:
render({children} = this.props) {
return (
<View style={styles.container}>
<View style={styles.tabsContainer}>
{children.map(({ props: { title } }, index) =>
<TouchableOpacity
style={[
// Default style for every tab
styles.tabContainer,
index === this.state.activeTab ? styles.tabContainerActive : []
]}
// Change active tab
onPress={() => this.setState({ activeTab: index }) }
// Required key prop for components generated returned by map iterator
key={index}
>
<Text style={styles.tabText}>
{title}
</Text>
</TouchableOpacity>
}
</View>
<View style={styles.contentContainer}>
{children[this.state.activeTab]}
</View>
</View>
);
};