React-Native: pressing the button twice only updates the this.setState - react-native

The App is simple.. Conversion of gas.. What im trying to do is multiply the Inputed Amount by 2 as if it is the formula. So i have a index.js which is the Parent, and the Calculate.component.js who do all the calculations. What i want is, index.js will pass a inputed value to the component and do the calculations and pass it again to the index.js to display the calculated amount.
Index.js
import React, { Component } from 'react';
import { Text } from 'react-native';
import styled from 'styled-components';
import PickerComponent from './Picker.Component';
import CalculateAVGAS from './Calculate.component';
export default class PickerAVGAS extends Component {
static navigationOptions = ({ navigation }) => ({
title: navigation.getParam('headerTitle'),
headerStyle: {
borderBottomColor: 'white',
},
});
state = {
gasTypeFrom: 'Gas Type',
gasTypeTo: 'Gas Type',
input_amount: '',
pickerFrom: false,
pickerTo: false,
isResult: false,
result: '',
};
inputAmount = amount => {
this.setState({ input_amount: amount });
console.log(amount);
};
onResult = value => {
this.setState({
result: value,
});
console.log('callback ', value);
};
render() {
return (
<Container>
<Input
placeholder="Amount"
multiline
keyboardType="numeric"
onChangeText={amount => this.inputAmount(amount)}
/>
<ResultContainer>
<ResultText>{this.state.result}</ResultText>
</ResultContainer>
<CalculateContainer>
<CalculateAVGAS
title="Convert"
amount={this.state.input_amount}
total="total"
titleFrom={this.state.gasTypeFrom}
titleTo={this.state.gasTypeTo}
// isResult={this.toggleResult}
result={value => this.onResult(value)}
/>
</CalculateContainer>
</Container>
);
}
}
CalculateAVGAS / component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
export default class CalculateAVGAS extends Component {
static propTypes = {
amount: PropTypes.string.isRequired,
titleFrom: PropTypes.string.isRequired,
titleTo: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
};
state = {
totalVisible: true,
result: '',
};
onPressConversion = () => {
const formula = this.props.amount * 2;
const i = this.props.result(this.state.result);
this.setState({ result: formula });
// console.log(this.state.result);
console.log('func()');
}
render() {
return (
<ButtonContainer onPress={() => this.onPressConversion()}>
<ButtonText>{this.props.title}</ButtonText>
</ButtonContainer>
);
}
}
After doing this, the setState only updates when pressing the Convert button twice

your issue here is that you want to display information in the parent component, but you are saving that info in the child component's state.
Just pass amount, and result, to the stateless child component (CalculateAVGAS).
It's usually best to keep child components "dumb" (i.e. presentational) and just pass the information they need to display, as well as any functions that need to be executed, as props.
import React, {Component} from 'react';
import styled from 'styled-components';
export default class CalculateAVGAS extends Component {
onPressConversion = () => {
this.props.result(this.props.amount * 2);
};
render() {
return (
<ButtonContainer onPress={() => this.onPressConversion()}>
<ButtonText>{this.props.title}</ButtonText>
</ButtonContainer>
);
}
}
const ButtonContainer = styled.TouchableOpacity``;
const ButtonText = styled.Text``;
Parent component looks like:
import React, {Component} from 'react';
import {Text} from 'react-native';
import styled from 'styled-components';
import CalculateAVGAS from './Stack';
export default class PickerAVGAS extends Component {
static navigationOptions = ({navigation}) => ({
title: navigation.getParam('headerTitle'),
headerStyle: {
borderBottomColor: 'white',
},
});
state = {
gasTypeFrom: 'Gas Type',
gasTypeTo: 'Gas Type',
input_amount: null,
pickerFrom: false,
pickerTo: false,
isResult: false,
result: null,
};
inputAmount = amount => {
this.setState({input_amount: amount});
};
onResult = value => {
this.setState({
result: value,
});
};
render() {
return (
<Container>
<Input
placeholder="Amount"
multiline
keyboardType="numeric"
onChangeText={amount => this.inputAmount(amount)}
/>
<ResultContainer>
<ResultText>{this.state.result}</ResultText>
</ResultContainer>
<CalculateContainer>
<CalculateAVGAS
title="Convert"
amount={this.state.input_amount}
total="total"
titleFrom={this.state.gasTypeFrom}
titleTo={this.state.gasTypeTo}
result={value => this.onResult(value)}
/>
</CalculateContainer>
</Container>
);
}
}
const Container = styled.View``;
const ResultContainer = styled.View``;
const ResultText = styled.Text``;
const CalculateContainer = styled.View``;
const Input = styled.TextInput``;

Related

Pass input value to store using react-redux payload and reducer

I have a Text Input and I am attempting to send the value inputted to the store in my reducer, then display that value on the screen.
It appears the input value is not even making it to the store...can someone explain why I am not dispatching this correctly?
e.target.value is throwing an error of Undefined.
COMPONENT
import React, { Component,setState } from 'react';
import { StyleSheet, View, Button, Text, StatusBar, TextInput, TouchableOpacity, Image } from 'react-native';
import { connect } from 'react-redux';
class Counter extends Component {
emailHandler() {
this.props.email();
}
toggleCounterHandler() {}
render() {
return (
<View>
<View ><Text style={styles.welcometext}>{this.props.value}</Text></View>
<View>
<TextInput
style={styles.input}
onChangeText= {(e) => this.props.emailHandler(e.target.value)}
value={this.props.value}
secureTextEntry={false}
/>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
value: state.value
};
}
const mapDispatchToProps = dispatch => {
return {
email: (value) => dispatch({
type: 'email',
payLoad: value
})
//() => dispatch({type: 'email', text: this.state.text})
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
REDUCER
import React from 'react';
import { createStore } from 'redux';
const counterReducer = (state = { email:'email' }, action) => {
if (action.type === 'email') {
return {
...state,
email: action.payload,
};
}
console.log(state);
return state;
};
const store = createStore(counterReducer);
export default store;
If you have a look at onChangeText api from the docs, it is passing the text straightaway as a param. Thus, you don't have to use e.target.value
Also second thing, I noticed that you mapDispatchToProps as email instead of emailHandler? So your redux function should be this.props.email
You don't have to create another function emailHandler to use the redux in props
you should do this:
import React, { Component,setState } from 'react';
import { StyleSheet, View, Button, Text, StatusBar, TextInput, TouchableOpacity, Image } from 'react-native';
import { connect } from 'react-redux';
class Counter extends Component {
toggleCounterHandler() {}
render() {
return (
<View>
<View ><Text style={styles.welcometext}>{this.props.value}</Text></View>
<View>
<TextInput
style={styles.input}
onChangeText= {(e) => this.props.email(e.target.value)}
value={this.props.value}
secureTextEntry={false}
/>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
value: state.value
};
}
const mapDispatchToProps = dispatch => {
return {
email: (value) => dispatch({
type: 'email',
payLoad: value
})
//() => dispatch({type: 'email', text: this.state.text})
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

MobX observable changes not updating components

I have a store in MobX that handles containing the cart, and adding items to it. However, when that data is updated the components don't re-render to suit the changes.
I tried using useEffect to set the total price, however when the cartData changes, the state is not updated. It does work on mount, though.
In another component, I call addItemToCart, and the console.warn function returns the proper data - but that data doesn't seem to be synced to the component. clearCart also seems to not work.
My stores are contained in one RootStore.
import React, {useState, useEffect} from 'react';
import {List, Text} from '#ui-kitten/components';
import {CartItem} from '_molecules/CartItem';
import {inject, observer} from 'mobx-react';
const CartList = (props) => {
const {cartData} = props.store.cartStore;
const [totalPrice, setTotalPrice] = useState(0);
const renderCartItem = ({item, index}) => (
<CartItem itemData={item} key={index} />
);
useEffect(() => {
setTotalPrice(cartData.reduce((a, v) => a + v.price * v.quantity, 0));
console.warn('cart changed!');
}, [cartData]);
return (
<>
<List
data={cartData}
renderItem={renderCartItem}
style={{backgroundColor: null, width: '100%'}}
/>
<Text style={{textAlign: 'right'}} category={'s1'}>
{`Total $${totalPrice}`}
</Text>
</>
);
};
export default inject('store')(observer(CartList));
import {decorate, observable, action} from 'mobx';
class CartStore {
cartData = [];
addItemToCart = (itemData) => {
this.cartData.push({
title: itemData.title,
price: itemData.price,
quantity: 1,
id: itemData.id,
});
console.warn(this.cartData);
};
clearCart = () => {
this.cartData = [];
};
}
decorate(CartStore, {
cartData: observable,
addItemToCart: action,
clearCart: action,
});
export default new CartStore();
I have solved the issue by replacing my addItemToCart action in my CartStore with this:
addItemToCart = (itemData) => {
this.cartData = [
...this.cartData,
{
title: itemData.title,
price: itemData.price,
quantity: 1,
id: itemData.id,
},
];
console.warn(this.cartData);
};

How to setting state with navigation params?

I am working on a React-native project with its basic packets(navigation etc). I have two screens. First there is a button and when i click the button. It's navigate to another screen which has flatlist. Then i click value in flatlist it is gives me a value . I can send that value to first screen with this.props.navigation.navigate and i can show it in console but i dont know how to use it to change buttonText which in my first screen? Where should i use setstate function in first screen ? (sorry for english)
Home.js
import React, {Component} from 'react';
import {View, Text} from 'react-native';
import {InputWithButton} from '../components/TextInput';
//const TEMP_BASE_CURRENCY = 'USD';
//const TEMP_CONVERT_CURRENCY = 'GBP';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
baseCurrency: 'TRY', //Başlangıç olarak sayfa açıldığında gelecek olan değerler
convertCurrency: 'USD',
amount: null,
result: '',
date: '',
};
//const selected = this.props.route.params;
}
calculate = () => {
const amount = this.state.amount;
let url =
'https://api.exchangeratesapi.io/latest?base=' + this.state.baseCurrency;
fetch(url, {
method: 'GET',
})
.then((res) => res.json())
.then((data) => {
const date = data.date;
const result = (
data.rates[this.state.convertCurrency] * amount
).toFixed(2);
this.setState({
result,
date,
});
})
.catch((error) => {
console.log(error);
});
};
handleChangeText = (text) => {
//Yazıda değişim algılandığında api işlemleri başlasın
this.setState(
{
amount: text,
},
this.calculate,
);
};
handlePressBaseCurrency = () => {
//flatlist sayfası açılsın
const {navigation} = this.props;
navigation.navigate('CurrencyList');
};
handlePressConvertCurrency = () => {
//flatlist sayfası açılsın
};
render() {
const {baseCurrency, convertCurrency, amount, result, date} = this.state;
return (
<View>
<InputWithButton
buttonText={baseCurrency}
onPress={this.handlePressBaseCurrency}
keyboardType="numeric"
onChangeText={(text) => this.handleChangeText(text)}
/>
<InputWithButton
editable={false}
buttonText={convertCurrency}
onPress={this.handlePressConvertCurrency}
value={result}
/>
</View>
);
}
}
export default Home;
CurrencyList.js
import React, {Component} from 'react';
import {View, FlatList, Text} from 'react-native';
import currencies from '../data/currencies';
import {ListItem, Separator} from '../components/List';
const temp_base_currency = 'CAD';
class CurrencyList extends Component {
constructor(props) {
super(props);
this.state = {
selectedItem: '',
};
}
handlePress = (item) => {
this.setState({
selectedItem: item, //__
});
// const {navigate} = this.props.navigation;
// navigate('Home', {clickedItem: this.state.selectedItem});
//Tıklandığında beklesin
setTimeout(
() => this.props.navigation.navigate('Home', {selected: item}),
1,
); //__
};
render() {
return (
<View>
<FlatList
renderItem={({item}) => (
<ListItem
onPress={() => this.handlePress(item)}
text={item}
selected={item === this.state.selectedItem} //__
/>
)}
data={currencies}
keyExtractor={(item) => item}
ItemSeparatorComponent={Separator}
/>
</View>
);
}
}
export default CurrencyList;
It would have been better if you shared your code but here is what I would do.
SECOND SCREEN
this.props.navigation.navigate('firstScreen', {
name: 'Your value'
})
FIRST SCREEN
const name = this.props.route.params.name;
<Button>{name}</Button
You can pass the selected item from the Flatlist to the Home screen like this:
Home.js:
this.props.navigation.navigate('CurrencyList',
{
onGoback: (item) => this.setState({})
})
CurrencyList.js:
handlePress: (item) => {
/** your code **/
this.props.navigation.state.params.onGoBack(item)
this.props.navigation.navigate('Home')
}

React StackNavigator with lifted state howto

I work with StackNavigator for navigation between a couple of screens. In these screens i will collect user information. I would like this information to be stored in one state so each view will have the same information.
I got this working but i think it is not the correct or ideal because i keep track of 2 states. One in the starting view and one in the current view.
Hence my question is, is it possible to work with only one state?
Here is my code:
My root class which creates the StackNavigator:
//#flow
import React from 'react';
import {StackNavigator} from 'react-navigation';
import Cart from './Cart';
import DeliveryAddress from './DeliveryAddress';
import Queue from './Queue';
export type State = {
text: string,
user: string,
onChange: Function
}
const Screens = StackNavigator({
Cart: {
screen: Cart
},
Queue: {
screen: Queue
},
DeliveryAddress: {
screen: DeliveryAddress
}
});
const AppNavigation = () => (<Screens/>);
export default class Root extends React.Component < Object,
State > {
render() {
return <AppNavigation/>;
}
}
Here my first view the Cart:
// #flow
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Text, View, Button} from 'react-native';
import DeliveryAddress from './DeliveryAddress';
import type {State}
from './Root';
export default class Cart extends Component < Object,
State > {
static navigationOptions = {
title: 'Welcome'
};
handleChange: Function;
getState: Function;
constructor(props : Object) {
super(props);
console.log("Construction Cart with state");
console.log(this);
console.log("props");
console.log(this.props);
this.handleChange = this.handleChange.bind(this);
this.state = {
user: "Lucy",
text: "Vul in",
onChange: this.handleChange,
getState: this.getState
};
}
handleChange(data : Object) {
this.setState(...data);
}
render() {
const {navigate} = this.props.navigation;
return (<View>
<Text>This is your cart.</Text>
<Button onPress={() => navigate('DeliveryAddress', this.state)} title="Go to delivery address"/>
</View>);
}
}
and here is my second view:
// #flow
import React, {Component} from 'react';
import {Text, View, Button, TextInput} from 'react-native';
import type {NavigationScreenProp}
from 'react-navigation/src/TypeDefinition';
import type {State}
from './Root';
type NavigationType = {
navigation: NavigationScreenProp < NavigationState >
}
type NavigationState = {
params: {
user: "bar"
}
}
export default class DeliveryAddress extends Component < any,
State > {
// Nav options can be defined as a function of the screen's props:
static navigationOptions = ({navigation} : NavigationType) => ({title: `Delivery address of ${navigation.state.params.user}`});
handleChange: Function;
constructor(props : Object) {
super(props);
this.state = props.navigation.state.params;
console.log(props.navigation.state);
this.handleChange = this.handleChange.bind(this);
}
handleChange(data : Object) {
this.setState(data);
this.state.onChange(data);
}
render() {
console.log("welkom in delivery address");
// The screen's current route is passed in to `props.navigation.state`:
const {navigate} = this.props.navigation;
return (<View>
<Text>User name: {this.state.user}</Text>
<TextInput style={{
height: 40,
borderColor: 'gray',
borderWidth: 1
}} onChangeText={(text) => this.handleChange({text})} value={this.state.text}/>
<Button onPress={() => navigate('Queue', this.state)} title={this.state.text}/>
</View>);
}
}
The thing is that i need to set both states like this:
handleChange(data : Object) {
this.setState(data);
this.state.onChange(data);
}
Is this the best way of doing this?
it seems that there is a nice way to solve this: Using Redux with a Provider.
with the provider it is possible do add information to a 'context':
import {Provider, connect} from "react-redux";
return (<Provider store={store}>
<AppNavigation/>
</Provider>);
the provider will add the store to the context. Any child (and grandchild) can use this store like this:
console.log("this.context");
console.log(this.context);
const {store} = this.context;
above will only work if you add this into the component:
Cart.contextTypes = {
store: PropTypes.object
}
here is more info:
https://egghead.io/lessons/react-redux-passing-the-store-down-implicitly-via-context

React Native - Cannot read property [PROPERTY] of undefined (ignite 2, reduxsauce)

I'm having trouble using redux in my react native app. I cannot call an action in my component. I get the following error:
This is my AuthRedux.js
import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'
const { Types, Creators } = createActions({
login: ['email', 'password'],
logout: null
})
export const AuthTypes = Types
export default Creators
export const INITIAL_STATE = Immutable({
isLoggedIn: false,
email: null,
password: null
})
export const userLogin = (state, {email, password}) => {
return Object.assign({}, state, {
isLoggedIn: true
});//state.merge({ isLoggedIn: true, email, password})
}
export const userLogout = (state) => {
return state.merge({ isLoggedIn: false, email: null, password: null })
}
export const reducer = createReducer(INITIAL_STATE, {
[Types.USER_LOGIN]: userLogin,
[Types.USER_LOGOUT]: userLogout
})
And this is my component LoginScreen.js
import React, { Component } from 'react'
import { ScrollView, Text, KeyboardAvoidingView, TextInput, TouchableOpacity, Button } from 'react-native'
import { connect } from 'react-redux'
import { AuthActions } from '../Redux/AuthRedux'
// Add Actions - replace 'Your' with whatever your reducer is called :)
// import YourActions from '../Redux/YourRedux'
// Styles
import styles from './Styles/LoginScreenStyle'
class LoginScreen extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
opacity: 1.0,
isLoggedIn: false
}
}
render () {
return (
<ScrollView style={styles.container}>
<KeyboardAvoidingView behavior='position'>
<Text>LoginScreen</Text>
<TextInput style={{width: 100, backgroundColor: 'red', height: 50, marginTop: 10}} onChangeText={(text) => this.setState({email : text})}/>
<TextInput style={{width: 100, backgroundColor: 'yellow', height: 50, marginTop: 10}} onChangeText={(text) => this.setState({password : text})}/>
<Button title='Hola' onPress={this.onLogin}/>
</KeyboardAvoidingView>
</ScrollView>
)
}
onLogin = () => {
console.log(this.state.email);
this.setState({opacity: 0.5})
this.props.userLogin(this.state.email, this.state.password);
}
handleOnPress = () => {
this.setState({opacity: 0.5})
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.auth.isLoggedIn
}
}
const mapDispatchToProps = (dispatch) => {
return {
userLogin: (email, password) => dispatch(AuthActions.login(email, password))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen)
I'm trying to call userLogin function from the onPress button which is assigned in mapDispatchToProps. I also have my rootReducer configured like this:
const rootReducer = combineReducers({
nav: require('./NavigationRedux').reducer,
github: require('./GithubRedux').reducer,
search: require('./SearchRedux').reducer,
auth: require('./AuthRedux').reducer
})
And the store is also given to the Provider in App.js
class App extends Component {
render () {
return (
<Provider store={store}>
<RootContainer />
</Provider>
)
}
}
I don't know why login action is not detected.
Instead of import { AuthActions } from '../Redux/AuthRedux', do import AuthActions from '../Redux/AuthRedux', because you are doing export default on the actionCreators which are the ones that you want to import right now.
You can also do export const AuthActions = Creators where you are doing export default Creators, and you can keep your import statement the same way you have right now.