I have two navigators one is stackNavigator and another is drawerNavigator.
what I want to do is dispatch an action and login is successfull and redirect the user to drawer navigator. I have used react-navigation.
What I have done is I am dispatching the action login success in saga.
Using NavigationActions.navigate({ routeName: 'drawerStack' }) to dispatch the action.
The action dispatches successfully but it doesn't navigate to drawerNavigator as shown in the picture below. What am I doing wrong?
saga.js
function* watchLoginRequest() {
while (true) {
const { state } = yield take(LOGIN_REQUEST);
try {
const payload = {
state
};
const response = yield call(loginCall, payload);
yield put(loginSuccess(response));
yield setUser(response.user);
yield put(NavigationActions.navigate({ routeName: 'drawerStack' }));
} catch (err) {
yield put(loginFailure(err.status));
}
}
}
drawerNavigation.js
// drawer stack
const DrawerStack = DrawerNavigator({
testComponent: { screen: TestComponent },
});
const DrawerNav = StackNavigator({
drawerStack: { screen: DrawerStack }
}, {
headerMode: 'float',
navigationOptions: ({ navigation }) => ({
headerStyle: { backgroundColor: 'green' },
title: 'Logged In to your app!',
headerLeft: <Text onPress={() => navigation.navigate('DrawerOpen')}>Menu</Text>
})
});
export default DrawerNav;
loginNavigation.js
// login stack
const LoginStack = StackNavigator({
startScreen: { screen: StartScreen },
loginScreen: { screen: LoginScreen },
personalInformation: { screen: PersonalInformation },
vehicleInformation: { screen: VehicleInformation },
availability: { screen: Availability },
selectRegisteration: { screen: SelectRegisteration },
serviceAddress: { screen: ServiceAddress },
}, {
headerMode: 'none',
transitionConfig: TransitionConfiguration
});
export default LoginStack;
ReduxNavigation.js
class ReduxNavigation extends React.Component {
constructor(props) {
super(props);
const { dispatch, nav } = props;
const navigation = ReactNavigation.addNavigationHelpers({
dispatch,
state: nav
});
this.state = {
loggedInStatus: false,
checkedSignIn: false
};
}
componentWillMount() {
isSignedIn()
.then(res => {
if (res !== null) {
this.setState({
loggedInStatus: true,
checkedSignIn: true
});
} else {
console.log(res);
}
})
.catch(err => console.log(err));
}
render() {
return <LoginNavigation navigation={this.navigation} />;
}
}
const mapStateToProps = state => ({ nav: state.nav });
export default connect(mapStateToProps)(ReduxNavigation);
To navigate to TestComponent you want your routeName to be testComponent, not the drawer stack. You navigate to specific screens, not navigation components.
Related
I have an application where a token get stored once the users open the app for the first time, and I need to check for that token when the app starts and create the navigator,
Approach 1
const getValue = async () => {
try {
value = await AsyncStorage.getItem('token').then((value) =>{
console.log(value)
})
} catch (error) {
console.log(error.message);
}
};
if (token != undefined) {
stackNavigator = createStackNavigator({
Products: Products,
Product: Product
}, {
headerMode: 'none',
navigationOptions: {
headerVisible: false
}
})
} else {
stackNavigator = createStackNavigator({
Home:Home,
Products: Products,
Product: Product
}, {
headerMode: 'none',
navigationOptions: {
headerVisible: false
}
})
}
In the first approach, it always renders the home screen even if the token is stored in the application
Second approach
const getValue = async () => {
try {
value = await AsyncStorage.getItem('token').then((value) =>{
if(value == null){
stackNavigator = createStackNavigator({
Home:Home,
Products: Products,
Product: Product
}, {
headerMode: 'none',
navigationOptions: {
headerVisible: false
}
})
}else{
stackNavigator = createStackNavigator({
Products: Products,
Product: Product
}, {
headerMode: 'none',
navigationOptions: {
headerVisible: false
}
})
}
})
} catch (error) {
console.log(error.message);
}
};
This approach throws Cannot read property of router undefined
Is there a way to get this to work?
Navigation.js
export const getValue = async () => {
let VALUE;
try {
await AsyncStorage.getItem('token')
.then((value) =>{
if(value == null){
VALUE = stackNavigator = createStackNavigator({
Home:Home,
Products: Products,
Product: Product
}, {
headerMode: 'none',
navigationOptions: {
headerVisible: false
}
})
}else{
VALUE = stackNavigator = createStackNavigator({
Products: Products,
Product: Product
}, {
headerMode: 'none',
navigationOptions: {
headerVisible: false
}
})
}
})
} catch (error) {
console.log(error.message);
}
return VALUE;
};
App.js
import { getValue } from './layout/navigation/Navigation';
let Navigation = createAppContainer(getValue());
class App extends React.Component {
render() {
return (<Navigation/>)
}
}
Create Routing
stackNavigator = createStackNavigator({
Home:Home,
Products: Products,
Product: Product,
Login : Login
},
{
headerMode: 'none',
initialRouteName : 'Home', // provide initial route : app will open with Home page
navigationOptions: {
headerVisible: false
},
})
// Export it with createAppContainer
export default createAppContainer(stackNavigator);
Import it to your App.js and use it as <Router/>
import Router from 'Application/navigation/routes';
Now what you can do is when user going to login then store their token to AsyncStorage and then redirect to home page.
In your home page you can add your token is exist or not code in mount lifecycle if you are not getting your token from storage then you can navigate your route to login screen.
I am fairly new to react-native but I have been able to make a login with authentication using firebase. Alongside this, I have used a drawer for further navigation. However, when a user is logged in I want them to be able to logout via a button on that drawer. Currently, when I click it is it doing nothing...
I have tried changing to the this.logout function to this.logout() which reaches the console.log("here) statement but then returns an undefined error. I have also played around with the bindings but with no results.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
}
this.logout = this.logout.bind(this)
};
componentDidMount() {
firebaseConfig.auth().onAuthStateChanged((authenticated) => {
authenticated ? this.setState(() => ({
authenticated: true,
})) : this.setState(() => ({
authenticated: false,
}));
});
}
//Logout of firebase and navigate to login screen
logout = () => {
console.log("here")
firebaseConfig.auth().signOut()
.then(() => this.props.navigation.navigate('Login'))
.catch((err) => {
console.log(err)
});
};
render() {
//const {currentUser} = this.state
return (
<Application/>
)
}
}
const DrawerComponents = (props) =>
<SafeAreaView style={styles.safeArea}>
<View style={styles.logo}>
</View>
<ScrollView>
<DrawerItems{...props}/>
<View style={{flex: 1}}>
<Button title="Logout" onPress={this.logout}/>
</View>
</ScrollView>
</SafeAreaView>
const TabNavigator = createDrawerNavigator({
Home: {screen: Homepage},
Profile: {screen: Profile},
Login: {
screen: Login,
navigationOptions: ({navigation}) => {
return {
drawerLabel: () => null,
drawerLockMode: 'locked-closed'
}
}
},
SignUp: {
screen: SignUp,
navigationOptions: ({navigation}) => {
return {
drawerLabel: () => null,
drawerLockMode: 'locked-closed'
}
}
},
Loading: {
screen: Loading,
navigationOptions: ({navigation}) => {
return {
drawerLabel: () => null,
drawerLockMode: 'locked-closed'
}
}
},
},
{
headerMode: null,
contentComponent: DrawerComponents,
drawerWidth: WIDTH,
drawerPosition: "right",
initialRouteName: 'Home',
contentOptions: {
activeTintColor: "orange"
}
});
const Application = createAppContainer(TabNavigator)
});
I am expecting that upon clicking logout, the currently logged in user on firebase is signed out and then is redirected to the login screen.
In React Native Navigation library 'react-navigation'
How could I set StackNavigator initialRouteName by AsyncStorage?
function getInitialScreen() {
AsyncStorage.getItem('initialScreen')
.then(screenName => {
return (screenName)
? screenName
: 'Login';
})
.catch(err => {});
}
const Navigator = StackNavigator({
Splash: { screen: Splash },
Login: { screen: Login },
WebPage: { screen: WebPage }
}, {
initialRouteName: getInitialScreen()
});
Changing InitialRouteName with multiple Route depending upon your requirement. I have got it working this way.
create router file import all your screens.
export a stateless function call it createRootNavigator with params as (load="<Your initial screen>")
export const createRootNavigator = (load="<Your Initial Screen>") => {
return stackNavigator({
Initialize: {
screen: Initialize,
},
Main: {
screen: Main,
},
{
initialRouteName: load
}
})
}
In your main app,
state = {
load: "<Your Initial Screen>"
}
eg:
state = {
load: "Initialize" // value is string
}
Set the state accordingly in componentDidMount() method. And finally render new layout.
render() {
const Layout = createRootNavigator(this.state.load);
<Layout />
}
The above method worked fine for me. Hope it helps somebody.
I’ve also had this problem and currently the only good solution is the following example:
const RootNavLogged = StackNavigator({
...
},{
initialRouteName : 'Home'
});
const RootNav = StackNavigator({
...
},{
initialRouteName : 'Login'
});
class App extends Component {
render(){
if (this.props.userLogged == true ){
return (
<RootNavLogged/>
)
} else {
return(
<RootNav/>
)
}
}
}
Full Solution from React Native Navigation on Restart:
const Navigator = StackNavigator({
InitialScreen: {
screen: InitialScreen
},
Splash: {
screen: Splash
},
LanguageStartup: {
screen: LanguageStartup
},
Login: {
screen: Login
},
Register: {
screen: Register
}
}, {initialRouteName: 'InitialScreen'});
export default Navigator;
My Initial Screen
import React, {Component} from 'react';
import {connect} from 'react-redux';
import * as GeneralPref from './../preferences/GeneralPref'
import Log from './../utils/Log'
import {AsyncStorage, View} from 'react-native';
import * as Pref from './../preferences/Preferences';
import {NavigationActions} from 'react-navigation'
const TAG = 'InitialScreen'
class InitialScreen extends Component {
static navigationOptions = {
header: false
};
componentWillMount() {
Log(TAG+' Mount')
const {navigate} = this.props.navigation;
GeneralPref
.getInitialScreen()
.then(value => {
Log(TAG+' Initial',value)
if (value != null) {
Log(TAG+' Initial',value)
return value
} else {
Log(TAG+' No Initial','Splash')
return 'Splash'
}
})
.then(screenName => this.props.navigation.dispatch(NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({routeName: screenName})]
})))
.catch(err => {
Log(TAG+' Initial Error',value)
this.props.navigation.dispatch(NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({routeName: 'Splash'})]
}))
});
}
render() {
return null;
}
}
export default InitialScreen;
then in Language Screen
changeLanguageTo(language) {
Log(TAG+'Change Language', "Change Language To: " + language.code);
// Log(TAG, 'Current State');
Log(TAG+' Language State', language);
GeneralPref.setInitialScreen('Login');
this
.props
.actions
.changeLanguage(language);
I18nManager.forceRTL(true);
// Immediately reload the React Native Bundle
RNRestart.Restart();
};
I would like after Login (Welcome) the user to navigate to Home. I reset the history so the user cannot go back like this:
const actionToDispatch = NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Home' })]
});
this.props.navigation.dispatch(actionToDispatch);
This works properly. After pressing Log Out the user should go back to Welcome but it's not working. Here's what exactly I am doing:
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Welcome' }),
]
});
this.props.navigation.dispatch(resetAction);
The error says that there is no route for 'Welcome'. Must be one of 'Main', 'Privacy', 'Terms' which are routes of one of the tabs in the Home. See them below:
const AppStack = StackNavigator({
Welcome: {
screen: Welcome
},
Home: {
screen: Tabs
}
}, {
initialRouteName: this.state.isLoggedIn ? 'Home' : 'Welcome',
headerMode: 'none'
}
);
export const ProfileStack = StackNavigator({
Profile: {
screen: Profile,
},
});
export const SettingsStack = StackNavigator({
Settings: {
screen: Settings,
},
}, {
});
export const InfoStack = StackNavigator({
Main: {
screen: Main,
},
Privacy: {
screen: Privacy
},
Terms: {
screen: Terms
}
});
const routeConfiguration = {
Profile: { screen: ProfileStack },
Settings: { screen: SettingsStack },
Info: { screen: InfoStack }
};
const tabBarConfiguration = {
tabBarOptions: {
activeTintColor: 'white',
inactiveTintColor: 'lightgray',
labelStyle: {
fontSize: Normalize(10),
fontFamily: Fonts.book
},
style: {
backgroundColor: Colors.greenLightGradient,
borderTopWidth: 1,
borderTopColor: Colors.tabGreenLine
},
}
};
export const Tabs = TabNavigator(routeConfiguration, tabBarConfiguration);
I found the solution here: https://github.com/react-community/react-navigation/pull/789.
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Welcome' }),
],
key: null
});
this.props.navigation.dispatch(resetAction);
key: null is the important part.
For anyone who is looking for React Navigation 3.x and 4.x solution
import {NavigationActions, StackActions} from 'react-navigation';
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({routeName: 'Home'})],
key: null,
});
this.props.navigation.dispatch(resetAction);
React Navigation move the reset method to StackActions since 3.x
replace home with any screen you want reset history for (v5)
import { CommonActions } from '#react-navigation/native';
navigation.dispatch(state => {
// Remove the home route from the stack
const routes = state.routes.filter(r => r.name !== 'Home');
return CommonActions.reset({
...state,
routes,
index: routes.length - 1,
});
});
For those who use version 5.x, Ahmed Mersal's solution is perfect.
Starting from that, we can do a reusable React hook:
import { useNavigation, CommonActions } from "#react-navigation/native";
import { useEffect } from "react";
export const useRemoveRouteFromState = (
routeName: keyof ReactNavigation.RootParamList
) => {
const navigation = useNavigation();
useEffect(() => {
navigation.dispatch((state) => {
const routes = state.routes.filter(({ name }) => name !== routeName);
return CommonActions.reset({
...state,
routes,
index: routes.length - 1,
});
});
}, [navigation]);
};
Then, inside a component, we just have to use it:
export const PurchaseScreen: React.FC = () => {
useRemoveRouteFromState('Home');
// ...
};
I would like to implement a multi step login, similar to slack (it first asks the domain name, then the email, and later the password) in react and react-native.
I would like to know what are the best practices to do it?
Should I use a router/navigation solution like ReactNavigation https://github.com/react-community/react-navigation?
That's one way to do it, sure. The way I would do it is a single component:
class LoginComponent extends React.Component {
constructor(props) {
super(props);
this.state = { stage: 0 };
}
onDomainSubmit(data) {
this.props.getDomain(data.domain).then((domain) => {
this.setState({ domain, stage: 1 });
});
}
render() {
const { stage, domain } = this.state;
if (stage === 0) {
return <GetDomainForm onSubmit={ this.onDomainSubmit }... />;
} else if (stage === 1) {
return <LoginToDomainForm domain={ domain }... />;
}
}
}
getDomain is an action creator which has been injected into the component via react-redux's connect - although that's not necessary.
there's less headache that way, and everything you need is contained within one component.
you can use SwitchNavigator.
import React, { Component } from 'react';
import { StackNavigator, TabNavigator, SwitchNavigator } from 'react-navigation';
import AuthLoadingScreen from '../views/users/auth';
import LoginScreen from '../views/users/login';
import RegisterScreen from '../views/users/register';
import ResetPasswordScreen from '../views/users/resetPassword';
import MainTabNavigator from './MainTabNavigator';
export const AuthStack = StackNavigator({
Auth: { screen: AuthLoadingScreen, navigationOptions: { header: null } },
Login: { screen: LoginScreen, navigationOptions: { header: null } },
Register: { screen: RegisterScreen, navigationOptions: { header: null } },
ResetPassword:{ screen: ResetPasswordScreen, navigationOptions: { header: null } }
},{
initialRouteName: "Auth"
});
export const AppStack = TabNavigator(
{ screen: MainTabNavigator, },
{ navigationOptions: {
headerStyle: { backgroundColor: '#f4511e', },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold', },
},
}
);
export const createRootNavigator = () => {
return SwitchNavigator(
{
SignedIn: { screen: AuthStack },
SignedOut: { screen: AppStack }
},
{
initialRouteName: "SignedIn"
}
);
};