pass multiple props by react navigation - react-native

I am new to react-native and trying to make a GSTCalculator App. I take values from the user on calculator screen and show calculated values on Result screen. My problem is that the calculated values don't show up immediately on the Result screen. I have even used async and await. But it only shows first calculated value.
calculate = async() => {
await this.calculateSum();
this.props.navigation.navigate('Result', {prodCost: this.state.prodCost,
mfdProfitMargin: this.state.mfdProfitMargin, mCost: this.state.mCost, whProfit: this.state.whProfit,
rtProfit: this.state.rtProfit, cgst: this.state.cgst, sgst: this.state.sgst, igst: this.state.igst,
igstV: this.state.igstV, m2wV: this.state.m2wV, w2rT: this.state.w2rT, whProfitV: this.state.whProfitV,
cgstV: this.state.cgstV, sgstV: this.state.sgstV, w2rV :this.state.w2rV, rtProfitV: this.state.rtProfitV })
}
calculateSum = () => {
this.setState({mCost: Number(this.state.prodCost) + Number(this.state.mfdProfitMargin)});
this.setState({igstV: (Number(this.state.mCost)*Number(this.state.igst))/100});
this.setState({m2wV: Number(this.state.mCost) + Number(this.state.igstV)});
this.setState({whProfitV: (this.state.m2wV)*Number(this.state.whProfit)/100});
this.setState({w2rT: Number(this.state.m2wV) + Number(this.state.whProfitV)});
this.setState({cgstV: (this.state.w2rT)*Number(this.state.cgst)/100});
this.setState({sgstV: (this.state.w2rT)*Number(this.state.sgst)/100});
this.setState({w2rV: Number(this.state.w2rT) + Number(this.state.cgstV) + Number(this.state.sgstV)});
this.setState({rtProfitV: (this.state.w2rV)*Number(this.state.rtProfit)/100});
}
export default class Result extends Component {
constructor(props) {
super(props);
this.state = { pCost: this.props.navigation.state.params.prodCost, pMargin: this.props.navigation.state.params.mfdProfitMargin,
mCost:this.props.navigation.state.params.mCost, igstP: this.props.navigation.state.params.igst,
igstV: this.props.navigation.state.params.igstV, cgstP: this.props.navigation.state.params.cgst,
cgstV: this.props.navigation.state.params.cgstV, sgstP: this.props.navigation.state.params.sgst,
sgstV: this.props.navigation.state.params.sgstV, m2wV: this.props.navigation.state.params.m2wV,
whProfit: this.props.navigation.state.params.whProfit, whProfitV: this.props.navigation.state.params.whProfitV,
w2rT: this.props.navigation.state.params.w2rT, w2rV: this.props.navigation.state.params.w2rV,
rtProfit:this.props.navigation.state.params.rtProfit, rtProfitV: this.props.navigation.state.params.rtProfitV
}
}
render() {
return(
<View>
<ImageBackground source={require('../Assets/login.jpg')} style={Styles.bgImage}>
<ScrollView contentContainerStyle={GStyle.container}>
<View>
<Text style={[Styles.heading, {marginTop: 20}]}>Manufacturer to Wholesaler:</Text>
<View style={GStyle.inputView}>
<Text style={[GStyle.text, {marginTop:10}]}>Production Cost</Text>
<Text style={GStyle.resultText}>{this.state.pCost}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>Profit Margin</Text>
<Text style={GStyle.resultText}>{this.state.pMargin}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>Manufacture Cost</Text>
<Text style={GStyle.resultText}>{this.state.mCost}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>IGST({this.state.igstP}%)</Text>
<Text style={GStyle.resultText}>{this.state.igstV}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>Invoice Value</Text>
<Text style={GStyle.resultText}>{this.state.m2wV}</Text>
</View>
</View>
<View>
<Text style={[Styles.heading, {marginTop: 20}]}>Wholesaler to Retailer:</Text>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>Cost to Wholesaler</Text>
<Text style={GStyle.resultText}>{this.state.m2wV}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>Profit Margin({this.state.whProfit}%)</Text>
<Text style={GStyle.resultText}>{this.state.whProfitV}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>Total</Text>
<Text style={GStyle.resultText}>{this.state.w2rT}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>CGST({this.state.cgstP}%)</Text>
<Text style={GStyle.resultText}>{this.state.cgstV}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>SGST({this.state.sgstP}%)</Text>
<Text style={GStyle.resultText}>{this.state.sgstV}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>Invoice Value</Text>
<Text style={GStyle.resultText}>{this.state.w2rV}</Text>
</View>
</View>
<View>
<Text style={[Styles.heading, {marginTop: 20}]}>Retailer to Consumer:</Text>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>Cost to Retailer</Text>
<Text style={GStyle.resultText}>{this.state.w2rV}</Text>
</View>
<View style={GStyle.inputView}>
<Text style={GStyle.text}>ProfitMargin({this.state.rtProfit}%)</Text>
<Text style={GStyle.resultText}>{this.state.rtProfitV}</Text>
</View>
</View>
</ScrollView>
</ImageBackground>
</View>
);
}
}

There is a lot of setting of state going I think we can reduce it and there is a little refactoring we can do to make your code easier to read.
Constructor
You are typing the same thing over and over again and it makes your constructor much harder to read than it needs to be.
In this example we have taken the the value this.props.navigation.state.params and set it to it's own variable. This means you don't have to keep typing the this.props.navigation.state.params over and over and over again.
constructor(props) {
super(props);
const params = this.props.navigation.state.params
this.state = {
pCost: params.prodCost,
pMargin: params.mfdProfitMargin,
mCost:params.mCost,
igstP: params.igst,
igstV: params.igstV,
cgstP: params.cgst,
cgstV: params.cgstV,
sgstP: params.sgst,
sgstV: params.sgstV,
m2wV: params.m2wV,
whProfit: params.whProfit,
whProfitV: params.whProfitV,
w2rT: params.w2rT,
w2rV: params.w2rV,
rtProfit:params.rtProfit,
rtProfitV: params.rtProfitV
}
}
If we use spread operator we can make this even easier, that is if you are willing to use the same name as in the object that you are passing.
constructor(props) {
super(props);
this.state = {
...this.props.navigation.state.params
}
}
However, if you use this method then you will find that some of the unique names you have chosen will be gone, replaced by the ones that are in the object that you are passing.
I am assuming that you would go with the first option (as it doesn't change the names) so I will base my next refactors on that.
Calculate Sum
There are too many setStates here, It takes time for state to set, so you could be using old values in your subsequent calculations so it is best not to use setState until you have performed all your calculations. If the values for each setState depend on the previous values then you could do something like this, only using values from the initial state where necessary.
calculateSum () => {
let mCost = Number(this.state.prodCost) + Number(this.state.mfdProfitMargin);
let igstV = mCost * Number(this.state.igst) / 100;
let m2wV = mCost + igstV;
let whProfitV = m2wV * Number(this.state.whProfit)/100;
let w2rT = m2wV + whProfitV;
let cgstV = w2rT * Number(this.state.cgst)/100;
let sgstV = w2rT * Number(this.state.sgst)/100;
let w2rV = w2rT + cgstV + sgstV;
let rtProfitV = w2rV * Number(this.state.rtProfit)/100;
return { mCost, igstV, m2wV, whProfitV, w2rT, cgstV, sgstV, w2rV, rtProfitV }
}
Here I return an object with all the updated values, that can be saved to state if we so choose.
Calculate
As the calculateSum function now returns an object with all the value that you want you could do something like this. We take the sum object that we just calculated and using a spread operator set all those objects to state. We then use a callback on the setState function to then navigate to the next page, using the spread operator again to capture all the values that in state.
calculate = () => {
let sum = this.calculateSum();
this.setState({...sum}, () => {
this.props.navigation.navigate('Result', {...this.state })
});
}
However if these values don't need to saved to state and just passed to the next screen, we could update the calculate function to something like this
calculate = () => {
let sum = this.calculateSum();
this.props.navigation.navigate('Result', {...this.state, ...sum })
}
I hope this helps you.
For more information on the spread operator in javascript see this article
https://zendev.com/2018/05/09/understanding-spread-operator-in-javascript.html
For more information on setting state check out both of these articles
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296

Related

Updating button state which is clicked, inside RenderItem of Flat-list, in React Native

I am new to React Native and trying to accomplish something like below, where simple list from server is rendering in a list with a button. Button, upon click, will be disabled and opacity will be changed.
I am able to create the UI but when I click any button that says Join, previous clicked button resets it state to original. In other words, only one button displays clicked state all the time.
so my code is something like
constructor(props){
super(props);
this.state = {selectedIndices: false, groupsData: ''};
}
Flatlist inside render method looks like
<FlatList style={styles.list}
data={this.state.groupsData}
keyExtractor={(groups) => {
return groups.groupId.toString();
}}
renderItem={(item, index) => {
return this.renderGroupItem(item);
}}/>
RenderGroupItem
renderGroupItem = ({item} )=>(
<GroupItem group = {item} style={{height: '10%'}} onPress = {() => this.onJoinButtonPress(item)}
index = {this.state.selectedIndices}/>
)
onJoinButtonPress
onJoinButtonPress = (item) =>{
this.setState({selectedIndices: true});
}
GroupItem
render(){
if(this.props.group.groupId === this.props.index){
return(
<View style = {[styles.container]}>
<Image source={{uri: 'some Image Url'}} style={styles.roundImage}/>
<Text style={styles.groupText}>{this.props.group.name}</Text>
<View >
<TouchableOpacity style = {[styles.button, {opacity: 0.4}]} activeOpacity = { .5 } onPress = {this.props.onPress}
disabled = {true}>
<Text style = {{color: 'white', fontSize: 12}}>Joined</Text>
</TouchableOpacity>
</View>
</View>
);
}else{
return(
<View style = {styles.container}>
<Image source={{uri: 'Some Image Url'}} style={styles.roundImage}/>
<Text style={styles.groupText}>{this.props.group.name}</Text>
<View >
<TouchableOpacity style = {styles.button} activeOpacity = { .5 } onPress = {this.props.onPress}>
<Text style = {{color: 'white', fontSize: 12}}>Join</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Now I know that I need to pass an array or hasmap which contains mapping of items that have been clicked but I dont know how to do that. Need desperate help here.
I was able to overcome above problem after maintaining a boolean in the groupsData. Upon selection, I update a boolean "groupsJoined" in groupsData and update the state of groupsData which will invoke render. Inside GroupsItem class, I added a check that if data from props has joinedGroups as true then render selected state else non selected state.
Courtesy https://hackernoon.com/how-to-highlight-and-multi-select-items-in-a-flatlist-component-react-native-1ca416dec4bc

How to use React Native Switch inside a functional component?

I am trying to use the Switch component provided by react-native, but it doesn't toggle.
function settings(props) {
let {changeView, header} = props;
let rememberPin = false;
let toggleRememberPin = (value) => {
rememberPin = value;
};
return (
<View style={styles.appContainer}>
<View style={styles.appBody}>
<View style={{flex:1, flexDirection: 'row',justifyContent:'center',alignItems:'center',width: Dimensions.get('screen').width,backgroundColor: Colors.white}}>
<Text>Remember PIN:</Text>
<Switch
onValueChange={toggleRememberPin}
value={rememberPin}
ios_backgroundColor="#aeaeae"
trackColor={{true: Colors.customerGreen, false: '#aeaeae',}}/>
</View>
</View>
</View>
);
}
I get the Switch rendered in the View, I can touch it and it moves from OFF to ON, but suddently it come back to OFF without keeping on ON state.
What's wrong?
You need to look into the core concepts of React, particularly how component state works.
In short, normal variable assignment doesn't cause a component to re-render and reflect changes. That's when you want to use the concept of state.
Here's how to do it properly:
function Settings(props) {
let {changeView, header} = props;
const [rememberPin, setRememberPin] = useState(false);
const toggleRememberPin = (value) => {
setRememberPin(value);
};
return (
<View style={styles.appContainer}>
<View style={styles.appBody}>
<View style={{flex:1, flexDirection: 'row',justifyContent:'center',alignItems:'center',width: Dimensions.get('screen').width,backgroundColor: Colors.white}}>
<Text>Remember PIN:</Text>
<Switch
onValueChange={toggleRememberPin}
value={rememberPin}
ios_backgroundColor="#aeaeae"
trackColor={{true: Colors.customerGreen, false: '#aeaeae',}}/>
</View>
</View>
</View>
);
}

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>
);
}

How to get the style name and state name of a clicked element in React Native?

In the following, first example how do I check if the clickable element TouchableWithoutFeedback has the style named EntryBlock1?
render() {
return (
<TouchableWithoutFeedback style={styles.EntryBlock1} onPress={() => this.CheckIfHasStyles()}>
<View style={styles.redundantWrapperNumber1}>
<Text style={styles.redundantWrapperNumber2}>Click To Test For Style</Text>
</View>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback style={styles.EntryBlock2} onPress={() => this.CheckIfHasStyle()}>
<View style={styles.redundantWrapperNumber1}>
<Text style={styles.redundantWrapperNumber2}>Click To Test For Style</Text>
</View>
</TouchableWithoutFeedback>
); // end return
} // end render
In the 2nd example, how can I check if TouchableWithoutFeedback has the state name EntryBlock1 ?
constructor (props) {
super(props);
this.state = {
EntryBlock1: [styles.entryBlockButton, styles.entryBlockButtonMin],
EntryBlock2: [styles.entryBlockButton, styles.entryBlockButtonMax],
}
}
render() {
return (
<TouchableWithoutFeedback style={this.state.EntryBlock1} onPress={() => this.CheckIfHasStateName()}>
<View style={styles.redundantWrapperNumber1}>
<Text style={styles.redundantWrapperNumber2}>Click To Test For Style</Text>
</View>
</TouchableWithoutFeedback>
); // end return
What I've Tried
Different stuff along the lines of the following..
CheckIfHasStyles =()=> {
var object = this.state;
var stringifiedObject = JSON.stringify(object);
var slicedObject = stringifiedObject.slice(2, 13);
if (slicedObject === 'EntryBlock1') {
alert('success');
} else {
alert('failure');
}
}
.. which actually works for one very specific example, but not when I have multiple state names as this.state gets all of them in one object and not just the state of the element clicked.
Note: My question is solely about getting attributes and their values. It is to help me develop a style of toggling styles on elements but I am only looking for attribute stuff in these answers as I would like to use attributes, if possible, for more than just toggling in the future.
You can pass it as parameter to your function directly like this:
render() {
return (
<TouchableWithoutFeedback style={styles.EntryBlock1} onPress={() => this.CheckIfHasStyles('EntryBlock1')}>
<View style={styles.redundantWrapperNumber1}>
<Text style={styles.redundantWrapperNumber2}>Click To Test For Style</Text>
</View>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback style={styles.EntryBlock2} onPress={() => this.CheckIfHasStyle('EntryBlock2')}>
<View style={styles.redundantWrapperNumber1}>
<Text style={styles.redundantWrapperNumber2}>Click To Test For Style</Text>
</View>
</TouchableWithoutFeedback>
);
}
and check if you are receiving any parameter in your function and do what you want
based on If-else condition

How can I add events in child component in listview? Is it possible to add event in listview?

' In My listview two component:first,second.
In the second there are map function return third (array elements).
How can I pass and the click event in the first, second, third & inner. For comments & sub comments in post. In the bellow updateComments is not working. And one thing, WallUpdates is also child component.'
class WallUpdates extends Component {
constructor(props) {
super(props);
this.updateComments = this.updateComments.bind(this);
}
updateComments(){
alert("updatecommentPost");
}
render() {
return(
<View>
<Text>
{this.props.allPost}
</Text>
<ListView
dataSource={this.props.dataSource} renderRow={this.renderMovie} style={styles.listView}
/>
</View>
);
}
renderMovie(list) {
var commentNodes = list.comment.map((item, i) => {
console.log(item);
console.log(item.commentcontent);
return (<Comments key={item.cid} comment={item.commentcontent} />);
});
//
return (
<View style={styles.container} >
<Text style={styles.year}>{list.postcontent + '\n\n'}</Text>
<TouchableOpacity onPress={this.updateComments}>
<View style={styles.buttonsmall}>
<Text style={styles.buttonsmallText}>comments!</Text>
</View>
</TouchableOpacity>
{commentNodes}
</View>
);
}
}
I have changed - renderMovie(list) to renderMovie = (list) => and also change onPress={this.updateComments} to onPress={() => { this.updateMe() }} & this is working for me .