React Native Navigation - 'cannot read property 'dispatch' of undefined' - reactjs

Trying to navigate to another screen but facing the issue. Code has been given below:
Routes.js
import { Drawer } from './Drawer';
import { ItemDetailsScreen } from '../screens/ItemDetailsScreen';
enter code here
export const App = createStackNavigator(
{
Drawer: {
screen: Drawer
},
ItemDetails: {
screen: ItemDetailsScreen
}
},
{
initialRouteName: "Drawer",
headerMode: "none"
}
)
Drawer.js
export const Drawer = createDrawerNavigator(
{
Home: { screen: ItemListScreen }
},
{
navigationOptions: () => ({
drawerLockMode: 'locked-closed'
})
initialRouteName: "Home",
drawerPosition: 'left',
contentComponent: props => <SideBar {...props} />
}
)
When trying to navigate 'ItemDetailsScreen' by calling following methods it shows me the error
onItemPress(item) {
const navigateAction = NavigationActions.navigate({
routeName: 'ItemDetailsScreen',
params: {item: item}
})
this.props.navigation.dispatch(navigateAction)
}
error is:
cannot read property 'dispatch' of undefined

Related

React navigation - how to nest drawerNavigator inside stackNavigator

I am using react-navigation, I want to open a drawer from MyProfile screen, having the options to go to EditProfile screen and Settings screen. But I can't figure out how to open a drawer when I click on MyProfile's header button.
App.js:
const MyProfileStack = createStackNavigator({
MyProfile: {
screen: profile,
navigationOptions: ({navigation}) => {
return {
title: "My Profile",
headerRight: (
<Icon type="evilicon" name="navicon" size={40}
onPress={() => navigation.dispatch(DrawerActions.openDrawer())}/>
)
};
}
}
})
const DrawerStack = createDrawerNavigator({
Edit: { screen: EditProfileStack }
Settings: { screen: SettingsStack }
})
const EditProfileStack = createStackNavigator({
EditProfile: {
screen: editProfile,
navigationOptions: ({navigation}) => {
return {
title: "Edit Profile",
headerLeft: (
<Icon type="evilicon" name="chevron-left" size={50}
onPress={() => navigation.navigate("MyProfile")}/>
)
};
}
}
});
const TabStack = createBottomTabNavigator({
Feed: { screen: FeedStack },
Profile: { screen: MyProfileStack },
});
const MainStack = createSwitchNavigator(
{
Home: TabStack,
Drawer: DrawerStack
},
{
initialRouteName: 'Home'
}
);
const AppContainer = createAppContainer(MainStack);
Solution
You need to put your MyProfileStack in DrawerStack as below.
const DrawerStack = createDrawerNavigator({
MyProfile: { screen: MyProfileStack }
Edit: { screen: EditProfileStack }
Settings: { screen: SettingsStack }
})
const TabStack = createBottomTabNavigator({
Feed: { screen: FeedStack },
Profile: { screen: DrawerStack },
});
const AppContainer = createAppContainer(MainStack);
You can use various combination.
Why?
SwitchNavigator resign other Screens when you switch to another one. So you cannot call drawer from the screen already resigned.
p.s: You can use navigation events if you want to refresh your screen when change the screen. Use onWillFocus.

React Native - NavigationActions.navigate() not navigating from within redux

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.

React Native StackNavigator initialRouteName

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

Reset navigation history to Login screen using react navigation

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');
// ...
};

How to implement a multi step login in react or react native

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

Resources