I am setting up a new route, and want to support about making new routes. What can I do?
My AppNavigator.js - Routes are in here
import { createStackNavigator, createAppContainer } from "react-navigation";
import Login from "../client/components/auth/Login";
import Register from "../client/components/auth/Register";
const AppNavigator = createStackNavigator({
Login: { screen: Login },
Register: { screen: Register }
});
const App = createAppContainer(AppNavigator);
export default App;
In App.js, I just imported AppNavigator
Register.js - I am trying to route to Login
import React from "react";
import {
StyleSheet,
Text,
View,
TextInput,
Button,
TouchableOpacity
} from "react-native";
import PropTypes from "prop-types";
export default class Register extends React.Component {
render() {
return (
<View style={styles.container}>
<Text onPress={() => this.props.navigation.navigate("Login")}>
Have account? Login
</Text>
</View>
);
}
}
Create a new stack with createStackNavigator. As far as I understand you want to redirect to another route after Log in. For that, you have to add them createSwitchNavigator. Then after successful login, you will redirect your route for example settings and for successful login you save the token in Asyncstorage. So, every time you load the app if you are successfully logged in you will be redirected to Setting page automatically otherwise the registration page will be loaded.
Here the code for App.js
App.js
import { createStackNavigator, createAppContainer,createSwitchNavigator } from "react-navigation";
import Login from "../client/components/auth/Login";
import Register from "../client/components/auth/Register";
import Setting from "../client/components/auth/Setting";
class AuthLoadingScreen extends React.PureComponent {
constructor(props) {
super(props);
this._bootstrapAsync();
}
async _getStoredDataFromAsync(key) {
return await AsyncStorage.getItem(key);
}
_bootstrapAsync = async () => {
const userToken = await this._getStoredDataFromAsync("userToken");
this.props.navigation.navigate(userToken ? 'App' : 'Some');
};
render() {
return (
<View style={{flex:1}}>
<ActivityIndicator />
</View>
);
}
}
const AppNavigator = createStackNavigator({
Login: { screen: Login },
Register: { screen: Register }
});
const SomeNavigator = createStackNavigator({
Setting: { screen: Setting},
});
const Navigation = createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppNavigator ,
Some: SomeNavigator ,
},
{
initialRouteName: 'AuthLoading',
});
const App = createAppContainer(Navigation );
export default App;
you can use react-native router flux for navigation
https://www.npmjs.com/package/react-native-router-flux
Related
I'm having an issue connecting to the redux store from my Settings.js page. The error I'm getting is:
"Invariant Violation: Invariant Violation: Could not find "store" in the context of "Connect(Settings)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(Settings) in connect options."
My SignIn.js page is connected the same way as the Settings.js page but the settings one does not seem to work while the SignIn.js page does work. My root component is wrapped in a Provider tag so I'm a bit lost. All pages should have access to the redux store. This might be because I'm using createSwitchNavigator between the authentication pages and app pages, or because I'm exporting/importing something incorrectly but I'm not too sure.
Any ideas?
Related Post: how to use Redux with createSwitchNavigator?
App.js
export default class App extends React.Component {
render() {
const authStack = createStackNavigator({
Onboarding: { screen: Onboarding },
SignUp: { screen: SignUp },
SignIn: { screen: SignIn },
});
const appStack = createBottomTabNavigator({
Home: { screen: Home },
Profile: { screen: Profile },
Settings: { screen: Settings }
});
const Navigation = createAppContainer(createSwitchNavigator(
{
AuthLoading: AuthLoad,
App: appStack,
Auth: authStack,
},
{
initialRouteName: 'AuthLoading',
}
));
return (
<Provider store={store}>
<Navigation/>
</Provider>
);
}
}
Settings.js [Not working]
class Settings extends Component {
render() {
return (
<View>
<Text>Settings Page</Text>
</View>
)
}
}
const mapStateToProps = state => ({
phone: state.user.phone,
});
export default connect(mapStateToProps)(Settings);
SignIn.js [Working]
class SignIn extends Component {
render () {
return (
<View>
<Text>SignIn Page</Text>
</View>
)
}
}
const mapStateToProps = state => ({
phone: state.user.phone,
});
export default connect(mapStateToProps)(SignIn);
It turned out to be an issue with webstorms auto import feature. It accidentally imported:
import connect from "react-redux/es/connect/connect";
Instead of:
import { connect } from 'react-redux';
The above implementation is correct.
What I want to do is wrapp on the very top of my app a switch navigator named MainNavigator which redirect to a LoadingScreen who determine if user is Logged or no and navigate to AuthNavigator or AppNavigator.
AuthNavigator himself is a stackNavigator who display screens 'LoginScreen' or 'SignUpScreen'.
AppNavigator is a DrawerNavigator with multiple screens (Profile, Home...).
The problem I encouter is that the return method seems to work, because I can see the logs in my console from the 'LoginScreen' component but on the screen, the only return is a white screen.. When I call directly my 'LoginScreen' component from the LoadingScreen component by using :
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away. switched to develop the app
this.props.navigation.navigate(userToken ? 'App' : 'LoginScreen');
};
I had the LoginScreen component rendered on my device.
I know the problem come from my AuthNavigator and AppNavigator because I can return screens components from AuthLoading or MainNavigator
Here some code :
* Main Navigator: entry point of the app
* Redirect to AuthLoadingScreen by default,
* if user is signed (AsyncStorage) or successly signIn
* redirect to AppNavigator else if user isn't SignIn
* go to AuthNavigator
import { createAppContainer, createSwitchNavigator } from 'react-
navigation';
import AuthNavigator from './auth/AuthNavigator'
import AppNavigator from './app/AppNavigator'
import AuthLoadingScreen from '../screens/auth/AuthLoadingScreen'
export default createAppContainer(createSwitchNavigator(
{
// You could add another route here for authentication.
// Read more at https://reactnavigation.org/docs/en/auth-flow.html
AuthLoading: AuthLoadingScreen,
App: AppNavigator,
Auth: AuthNavigator,
},
{
initialRouteName: 'AuthLoading',
}
));
AuthLoading :
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
StatusBar,
StyleSheet,
View,
Text
} from 'react-native';
export default class AuthLoadingScreen extends React.Component {
constructor(props) {
super(props);
this._bootstrapAsync();
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away. switched to develop the app
this.props.navigation.navigate(userToken ? 'App' : 'Auth');
};
// Render any loading content that you like here
render() {
return (
<View>
<Text>Loading</Text>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
AuthNavigator:
import { createStackNavigator } from 'react-navigation';
import LoginScreen from '../../screens/auth/LoginScreen';
import SignUpScreen from '../../screens/auth/SignUpScreen';
export default createStackNavigator({
Login : {
screen : LoginScreen
},
SignUp: {
screen : SignUpScreen
}
},
{
initialRouteName: 'Login',
})
LoginScreen:
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default class LoginScreen extends Component {
render() {
console.log('LOGIN SCREEN')
return (
<View style={styles.container}>
<Text style={styles.text}>Login</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
text : {
fontSize: 20,
}
});
I can't find the error, but I know it comes from the AuthNavigator and AppNavigator. I can see the 'LOGIN SCREEN' or 'APP SCREEN' if I set the AuthLoading route to render App in my console but nothing on the screen, when I init a new route LoginScreen in my MainNavigator and navigate to that route from the AuthLoading it works, I have a beautiful text displayed on my screen.
I Know it's probably an insignifiant error but I spend hours to fix this by myself and can't find, so if someone can help me it would be nice!
Thanks by advance!
Problem solved... Because at the top of my app in App.js I wrapped my MainNavigator like that
<View>
<StatusBar hidden={true} />
<MainNavigator />
</View>
So I remove the view and statusbar... and it works. 4 days to figure it out... I love it! :')
I have an app with this kind of structure
It doesn't matter if user is logged in or not, the TabBar always should be the same. In case user logged in app will open the content of the tab, otherwise Auth screen. How can I create this in react-navigation?
Sorry, slightly confused in react-navigation.
So I think I have a solution.
Create an event emitter that will emit events when the login state changes
Subscribe to the events in the App.js
Pass the updated events via screenProps to the TabNavigator.
These updated screenProps allow toggling between logged in states in each tab.
Store the logged in state in AsyncStorage so that it will be persisted between app closes. Note the AsyncStorage only allows you to store strings.
Send events from the AuthScreen when the user logs in, or from the other screens when the user logs out.
Event Emitter
To create an event emitter we need to use the events dependency. Install it with npm i events. We will create it as a singleton so that we can only have one instance of this running in our app.
import { EventEmitter } from 'events';
class LoginEventEmitter {
constructor () {
this.eventEmitter = new EventEmitter();
}
// create a function to handle the login
// pass the loggedIn value that you want to be emitted
handleLogin = (loggedIn) => {
this.eventEmitter.emit('loggedIn', { loggedIn });
}
}
const EventEmitterController = new LoginEventEmitter();
export default EventEmitterController;
App.js
Subscribe to the events and handle AsyncStorage
import React from 'react';
import { AsyncStorage } from 'react-native';
import AppContainer from './MainNavigation';
import LoginEventEmitter from './LoginEventEmitter';
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = {
loggedIn: false,
loaded: false
};
// subscribe to the events and update the values in state and in AsyncStorage
LoginEventEmitter.eventEmitter.addListener('loggedIn', e => {
this.setState({ loggedIn: e.loggedIn });
let value = e.loggedIn ? 'true' : 'false';
AsyncStorage.setItem('loggedIn', value);
});
}
async componentDidMount () {
// handle the loggedIn value when the component mounts
try {
let loggedIn = await AsyncStorage.getItem('loggedIn');
if (loggedIn) {
this.setState({
loggedIn: loggedIn === 'true',
loaded: true
});
} else {
this.setState({ loaded: true });
}
} catch (error) {
console.warn(error);
}
}
render () {
// wait until asyncstorage has returned a value before showing the App.
// pass the loggedIn value via screen props so every screen in the TabNavigator gets updated with the new value
if (this.state.loaded) {
return (
<AppContainer screenProps={{ loggedIn: this.state.loggedIn }}/>
);
} else {
return null;
}
}
}
Template for Tab Screen
This is a basic template for each TabScreen. I have put a logout button on it so that the user can logout. (This is mainly for testing).
import React from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import AuthScreen from './AuthScreen';
import LoginEventEmitter from './LoginEventEmitter';
export default class Screen1 extends React.Component {
logout = () => {
LoginEventEmitter.handleLogin(false);
}
render () {
if (this.props.screenProps.loggedIn) {
return (
<View style={styles.container}>
<Text>Screen 1</Text>
<Button title={'logout'} onPress={this.logout} />
</View>
);
} else {
return <AuthScreen />;
}
}
}
Notice in the render function that we access the screenProps that are passed from the TabNavigator depending on the value that is passed depends on what is rendered.
A sample AuthScreen
Here we perform a "login" when the user taps the button. This emits an event that is caught by the listener in the App.js which in turn updates the state value in the App.js and that value is passed via screenProps to each Tab.
import React from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import LoginEventEmitter from './LoginEventEmitter';
export default class Screen1 extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>AuthScreen</Text>
<Button title={'Login'} onPress={() => {
LoginEventEmitter.handleLogin(true);
}}/>
</View>
);
}
}
Sample TabNavigator
Here is a sample TabNavigator.
import Screen1 from './Screen1';
import Screen2 from './Screen2';
import { createBottomTabNavigator, createAppContainer } from 'react-navigation';
const screens = {
Screen1: {
screen: Screen1
},
Screen2: {
screen: Screen2
}
};
const config = {
headerMode: 'none',
initialRouteName: 'Screen1'
};
const MainNavigator = createBottomTabNavigator(screens, config);
export default createAppContainer(MainNavigator);
Snack
Finally here is a snack showing it all working https://snack.expo.io/#andypandy/event-emitter-to-handle-login
We are creating the AppStack and Auth Stack for our application using createSwitchNavigator
our switch component looks like
import React,{Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import {
createSwitchNavigator,
createStackNavigator,
createAppContainer
} from 'react-navigation';
import LoginForm from './Forms/LoginForm';
import ShellForm from './Forms/ShellForm';
AuthStack = createStackNavigator({ Login: LoginForm });
AppStack = createStackNavigator({ Shell: ShellForm});
const AppContainer = createAppContainer(
createSwitchNavigator(
{
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'Auth',
}
));
export default class RootAppContainer extends React.Component {
render() {
return <AppContainer />
}
}
And in the shell we are creating the dynamic drawer menu. The initial app will load Auth Stack where we have login form and Forgot password.
Since we imported the ShellForm in the Header on js load it fires all the const variable and tries to fetch the menu.
our shell form looks like
getAppContainer = () =>{
// get menus from we depends on the User logged in
var MenuBarInfo = require('../JsonData/Menu.json');
getMenus({}, getMenuSuccess, getMenuFailure);
var drawerlist = this.getDrawerListItems(MenuBarInfo.MenuItems)
var drawerOptions = {
contentComponent: MenuComponent,
initialRouteName:'Home',
drawerBackgroundColor: 'white',
drawerWidth: 300,
navigationOptions: {
gesturesEnabled: true,
}
}
drawer = createDrawerNavigator(
drawerlist ,
drawerOptions
);
return createAppContainer(drawer);
}
const DrawerNav = getAppContainer();
since the DrawerNav is a const variable it trying to prepare the menu is there any way to achieve this and avoid this loading upfront.
is there any way to start rendering the ShellForm only after the Login is done.
I have an Authentication screen like this :
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Profile from './Profile';
import LoginOrSignup from './LoginOrSignup';
class Auth extends Component {
render() {
if (this.props.isLoggedIn) {
return <Profile />;
} else {
return <LoginOrSignup />;
}
}
}
const mapStateToProps = (state, ownProps) => {
return {
isLoggedIn: state.auth.isLoggedIn
};
}
export default connect(mapStateToProps)(Auth);
On my LoginOrSignup screen i have a submit button contains a function to navigate like this:
this.props.navigation.navigate("ConfirmOTP");
But I always got this error :
undefined is not an object(evaluating 'this.props.navigation.navigate')
Then I tried to changed my Authentication screen like this
.....
<LoginOrSignup navigation={this.props.navigation}/>
.....
The error is gone, but it doesn't navigate to the next screen.
I'm still learning. Any helps would be really appreciated.
you need to register your sreen to a Route. This can be an example using createStackNavigator of react-navigation.
import ConfirmOtp from "./ConfirmOtp";
import { createStackNavigator } from "react-navigation";
class LoginOrSignup extends Component {
render() {
return (
<View>
<Button onClick={() => this.props.navigation.navigate("ConfirmOtp")}/>
</View>
)
}
}
export default createStackNavigator({
LoginOrSignup {
screen: LoginOrSignup
},
ConfirmOtp: {
screen: ConfirmOtp
}
});