React StackNavigator with lifted state howto - react-native

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

Related

null is not an object (evaluating 'this.nativeCommandsModule.push')

I am using WIX-React-Native-Navigation and while pushing a component in the stack I am getting this error.
Error
One thing to say, I am using Viro-React ARScene Navigation (Scene Navigation) in WIX-React-Native-Navigation.
Here's my code
index.js
import { AppRegistry } from 'react-native';
var OpenDoor = require('./app/base')
import Stack from './app/navigation';
import { Navigation } from "react-native-navigation";
AppRegistry.registerComponent('ViroSample', () => OpenDoor);
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
stack: Stack
}
});
});
navigation.js
import { Navigation } from "react-native-navigation";
import Base from './base';
import Menu from './components/Menu/menu';
Navigation.registerComponent('Base', () => Base);
Navigation.registerComponent('Menu', () => Menu);
const Stack = {
children: [{
component: {
name: 'Base'
},
}
]
};
export default Stack;
base.js
import React, {Component} from 'react';
import {
Alert,
View
} from 'react-native';
import {ViroARSceneNavigator} from 'react-viro';
import { Navigation } from 'react-native-navigation';
import APIKeys from './index';
import Camera from './components/Camera/camera';
import MenuButton from './components/Menu/menu_button';
class Base extends Component {
constructor(props) {
super(props);
this.navigateScreen = this.navigateScreen.bind(this);
}
navigateScreen(location) {
Navigation.push(this.props.componentId, {
component: {
name: location
}
});
}
render() {
return(
<View style={styles.container}>
<ViroARSceneNavigator
initialScene = {{
scene: Camera
}}
apiKey = {APIKeys.ViroReact}
style = {styles.ar_scene}
/>
<MenuButton navigation={this.navigateScreen}/>
</View>
);
}
};
const styles = {
container: {
flex: 1
}
};
export default Base;
module.exports = Base;
Correct me, If I am wrong somewhere.
What I am doing is, creating an application where users can decorate home virtually with the help of Augmented Reality. The mobile app is created on React-Native while the library used for augmented reality is Viro-React.
So, When I want to navigate from One Screen to another, then while pushing component in the stack I am getting this error.
Thanks.

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

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``;

Navigator Trouble

Good afternoon, actually i m keeping error while try to navigate.
This is my App.js
import { createStackNavigator, createAppContainer } from "react-navigation";
import Login from './src/Login';
import NuovoAccount from './src/NuovoAccount';
import HomePage from './src/HomePage';
import Lista_Sveglie from './src/Lista_Sveglie';
import NuovoAccount_2 from './src/NuovoAccount_2';
import Nuova_sveglia from './src/Nuova_sveglia';
import ScreenSveglia from './src/ScreenSveglia';
import Registra from './src/Registra';
import temp from './src/temp';
import Account from './src/Menu/Account';
import ElencoUtenti from './src/Menu/ElencoUtenti';
import MenuImpostazioni from './src/Menu/MenuImpostazioni';
import SelUtenteDest from './src/Menu/SelUtenteDest'
import Accedi from './src/Accedi'
import Landing from './Landing'
const AppNavigator = createStackNavigator(
{
Home: Landing,
Accedi : Accedi ,
Login: Login,
NuovoAccount: NuovoAccount,
HomePage: HomePage,
Lista_Sveglie: Lista_Sveglie,
NuovoAccount_2: NuovoAccount_2,
Nuova_sveglia: Nuova_sveglia,
ScreenSveglia: ScreenSveglia,
Registra: Registra,
temp: temp,
MenuImpostazioni: MenuImpostazioni,
ElencoUtenti: ElencoUtenti,
Account: Account,
SelUtenteDest: SelUtenteDest,
},
{
initialRouteName: "Home",
defaultNavigationOptions: {
headerTintColor: '#ccc',
headerStyle: {
borderBottomWidth: 4,
borderBottomColor: '#80ba27',
backgroundColor: '#364054',
},
headerTitleStyle: {
textAlign: 'center',
flex: 1
},
}
}
);
export default createAppContainer(AppNavigator)
and here my Landing.js
import React, { Component } from 'react';
import { StyleSheet, Text, View, Alert, Button } from 'react-native';
import * as firebase from 'firebase';
import HomePage from './src/HomePage';
import Accedi from './src/Accedi'
import { createStackNavigator, createAppContainer } from "react-navigation";
export default class Landing extends Component {
constructor() {
super();
this.state = {
loggedIn: false,
}
}
componentWillMount() {
console.log("QUI ENTRA")
---some firebase config ...
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ loggedIn: true })
} else {
this.setState({ loggedIn: false })
}
})
}
render(){
if (this.state.loggedIn){
return <HomePage/>
}else{
return <Accedi/>
}
}
}
Here my HomePage
<View>
<Text
onPress={() => this.props.navigation.navigate('MenuImpostazioni')}
style={style.footer_text_icone}>Configura Impostazioni</Text>
</View>
Can anyone tell me how to fix it and explain why this error ?
from documentation i saw this.props. is created on createnavigator function
"undefined is not an object - this.prop.navigation.navigate"
The problem here is that your HomePage component isn't a direct child of your stackNavigator. You need to pass it trough props to your child component doing:
render(){
if (this.state.loggedIn){
return <HomePage navigation={this.props.navigation}/>
}else{
return <Accedi/>
}
}
Inside the render function of your Landing.js
Then your child component will have access to it.
This is happening because you are using the same component, both as screen that as child component.
{
Home: Landing, //Here (as child)
Accedi : Accedi ,
Login: Login,
NuovoAccount: NuovoAccount,
HomePage: HomePage, //Here
Lista_Sveglie: Lista_Sveglie,
NuovoAccount_2: NuovoAccount_2,
Nuova_sveglia: Nuova_sveglia,
ScreenSveglia: ScreenSveglia,
Registra: Registra,
temp: temp,
MenuImpostazioni: MenuImpostazioni,
ElencoUtenti: ElencoUtenti,
Account: Account,
SelUtenteDest: SelUtenteDest,
}
react-navigation doesn't give access to navigation to a component just because it has been declared inside of it. By doing that if/else you are actually creating a whole new instance of that same component, with the only difference that it do not have access to this.props.navigation.
Landing.js isn't a screen that is defined within your stack navigator, so it does not have access to this.props.navigation.
If you don't want Landing.js within the stack navigator, you can follow this tutorial to use navigate and other navigation actions from any component: https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html

Undefined Unstated Container in a React Native Component using React Navigation

My problem is That I want to access a Container in a component but it seems to be undefined.
undefined alert image
I am using Unstated and as you can see this is my code in the container file (login-container.js):
import { Container } from 'unstated'
class LoginContainer extends Container {
constructor(props){
super(props)
this.state = {
stepNumber: 0,
}
}
}
export default new LoginContainer()
And this is app.js:
import React, { Component } from 'react'
import { createStackNavigator, createSwitchNavigator } from 'react-navigation'
import { Provider } from 'unstated'
import LoginContainer from './containers/login-container'
import Home from './screens/home'
import Splash from './screens/splash'
import Login from './screens/login'
import Intro from './screens/intro'
export default class App extends Component {
render() {
return (
<Provider inject={[LoginContainer]}>
<AuthStack/>
</Provider>
)
}
}
const SplashStack = createStackNavigator(...)
const AppStack = createStackNavigator(...)
const AuthStack = createStackNavigator(
{
Intro: { screen: Intro},
Login: { screen: Login}
},
{
headerMode: "none",
initialRouteName: "Intro"
}
)
const SwitchNavigator = createSwitchNavigator(...)
And this would be login.js:
import React, { Component } from 'react'
import { Text, View } from 'react-native'
export default class Login extends Component {
constructor(props){
super(props)
}
render() {
// const { state: {stepNumber} } = this.props.loginContainer
alert(this.props.LoginContainer)
return (
<View>
<Text> someText </Text>
</View>
)
}
}
I previously tried to use Subscribe component to inject the container to my app but I got the same thing I am getting here.
Using
- react-native 0.58.6
- react-navigation 2.13.0 (due to some bugs in v3)
- unstated 2.1.1
What's really great about Unstated is how simple it is to implement.
Just wrap your render component in Unstated's <Subscribe to></Subscribe> tags and you're good to go. Whenever you setState() in the Container, all Components that Subscribe to it get re-rendered with the Container's updated state property values available to them.
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import LoginContainer from './containers/login-container';
export default class Login extends Component {
constructor(props){
super(props)
}
render() {
return (
<Subscribe to={[LoginContainer, AnotherContainer]}>
{(container, another) => (
<View>
<Text>{container.state.stepNumber}</Text>
</View>
})
</Subscribe>
);
}
}
UPDATE: Or do it in this HOC way. After creating this:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import DefaultStore from "../store/DefaultStore";
const withUnstated = (
WrappedComponent,
Stores = [DefaultStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Then wrap your component like so:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import LoginContainer from './containers/login-container';
import AnotherContainer from './containers/another-container';
class Login extends Component {
constructor(props){
super(props)
}
render() {
const {LoginContainer: container} = this.props;
return (
<View>
<Text>{container.state.stepNumber}</Text>
</View>
);
}
}
export default withUnstated(Login, [LoginContainer, AnotherContainer])

undefined is not an object this.props

I try to use navigation between screen in my RN app. This is my code :
INDEX.ANDROID.JS :
import React, { Component } from 'react';
import {
AppRegistry,
Text,
View ,
Button
} from 'react-native';
import {StackNavigator} from 'react-navigation';
import Login from './app/components/Todo';
const SimpleApp = StackNavigator({
Login: { screen: Todo },
});
export default class aap extends Component {
static navigationOptions = { title: 'Welcome', };
render() {
const { navigate } = this.props.navigation;
return (
<Button onPress ={() => navigate('Todo') } title="go"/>
);
}
}
AppRegistry.registerComponent('aap', () => aap);
here is the code of the second screen TODO.JS
import React, { Component } from 'react';
import {
AppRegistry,
Text,
View ,
Button
} from 'react-native';
export default class Todo extends Component {
render() {
return (
<View>
<Text>
Here is my text
</Text>
</View>
);
}
}
When running my code i get an error : undefined is not an object this.props.naviagtion.
Any help is appreciated
You are not using StackNavigator correctly.
Like #xght said, you should be registering SimpleApp instead of aap. Also, you should be using aap as the initial route to your StackNavigator SimpleApp.
This should look something like this:
import React, { Component } from 'react';
import {
AppRegistry,
Text,
View ,
Button
} from 'react-native';
import {StackNavigator} from 'react-navigation';
import Todo from './app/components/Todo';
class aap extends Component {
static navigationOptions = { title: 'Welcome', };
render() {
const { navigate } = this.props.navigation;
return (
<Button onPress ={() => navigate('Todo') } title="go"/>
);
}
}
const SimpleApp = StackNavigator({
Login: { screen: aap },
Todo: { screen: Todo },
});
AppRegistry.registerComponent('aap', () => SimpleApp);
You registered the wrong component, so the navigator SimpleApp doesn't pass the navigation prop to your component.
Replace
AppRegistry.registerComponent('aap', () => aap); by:
AppRegistry.registerComponent('aap', () => SimpleApp);
And also you forgot to add your aap component in SimpleApp routes. And your import Login from './app/components/Todo';is wrong: Login is the name of the route in SimpleApp, and Todo is the name of the component, so you need to replace it by import Todo...