Two routes instantiate when "Registration" is triggered - react-native

I have an app with a registration and a login flow. And I'm having bit of an issue with nested stack navigators when used with Redux.
React-Navigation structure
RootNavigator (Stack navigator)
Registration (Stack navigator)
Login (Stack navigator)
App setup
react: 16.0.0-alpha.6,
react-native: 0.44.2,
react-navigation: ^1.0.0-beta.11,
react-redux: ^5.0.6,
My issue is that when 'Registration' (or 'Login') is triggered from 'Landing' ReactNavigation instantiate two initial screens for the nested (internal) stack navigator. Please see the screen grabs bellow.
This is the landing state (which is correct)
But when 'Register' is pressed, this is what happens.
I end up with two routes for 'Registration' (routeName is called 'PersonalRego'). As shown in the screen shot, 'Step1' is duplicated where as I was expecting to only have one 'Step1' instance in the routes array. But the keys for those two 'Step1' are different. One starts with 'Init-' the other one doesn't.
As a result, navigating back (pressing 'Close' button on top) does not dismiss registration as per expectation. It rather shows the 'Step1' of registration again.
Other than this issue, everything with react-navigation seems to be workinf fine for me. Is there anything special that I need to do when configuring nested StackNavigators in a ReactNative + ReactNavigation + Redux app?
I'm a bit baffled by this. Any comments are gladly welcomed.
P.s : StackNavigator code (just one level deep nested StackNavigator)
// LandingNav.js
const routes = {
Landing: { screen: Landing },
PersonalRego: { screen: PersonalRego },
Auth: { screen: Auth },
};
const config = {
headerMode: 'none',
initialRoute: 'Landing,
};
const Root = StackNavigator(routes, config);
.
// PersonalRegoNav.js
const routes = {
Step1: { screen: Step1 },
Step2: { screen: Step2 },
Step3: { screen: Step3 },
};
const config = {
headerMode: 'none,
initialRoute: 'Step1',
};
const Root = StackNavigator(routes, config);
.
// AuthNav.js
const routes = {
Login: { screen: LoginContainer },
ForgotUsername: { screen: ForgotUsername },
ForgotPassword: { screen: ForgotPassword },
};
const config = {
headerMode: 'none',
initialRoute: 'Login',
};
const Root = StackNavigator(routes, config);

Answering my own question for posterity's sake.
When Register is pressed, I'm navigating to 'Step1' instead of 'PersonalRego'. 'PersonalRego' already has 'Step1' as an initial route, so if you navigate to 'Step1', it pushes another instance.
So calling 'PersonalRego' route on Register button press fixes the issue.

Please refer to the link below, beautifully solved & explained by my friend -> https://medium.com/#kakul.gupta009/custom-drawer-using-react-navigation-80abbab489f7

Related

Authentication Flow in React Native

I'm having issue creating the authentication flow of my app. What I actually want is to navigate user conditionally based on their role.
By default, I have created an AuthStack which is basically a stackNavigator and it has a login page. Once user logs in, we receive user's role through a network request. Next I navigate him to a simple home page that returns nothing but switchNavigator based on his role. Here's the code for better clarity.
const AuthStack = createStackNavigator({
Login: {
screen: Login
},
SignUp: {
screen: SignUp
},
Home: {
screen: Home
}
},
{
initialRouteName: 'Login',
headerMode: 'none'
});
const AppContainer = createAppContainer(AuthStack);
const Navigation = () => {
return <AppContainer />
}
When user logs in, I redirect him to Home screen shown in above mentioned stack. Here's the code in Home screen:
const Home = (props) => {
const AppContainer = createAppContainer(RootNavigator(props.user.role))
return <AppContainer />
}
Here I create a new app container (which might be the bad practice, advice please). And RootNavigator is a helper function which returns a switchNavigator:
export const RootNavigator = (user) => {
return createSwitchNavigator({
Admin: {
screen: AdminDrawerNavigator
},
Reporter: {
screen: ReporterDrawerNavigator
}
},
{
initialRouteName: user === 'admin'? 'Admin': 'Reporter'
})
}
This all works fine but it seems switchNavigator doesn't seem to work correctly. If I press back button of hardware, it goes back to login page. Thank you for your time. Please suggest the possible solution.
Try to reset you history navigation :
NavigationActions.reset({
index: 1,
actions: [NavigationActions.navigate({
routeName: 'Admin'
}),
]
});

React Navigation - goBack() is automatically invoked for stack navigator screen

I have 3 stack navigator screens (Home, Item List, Item Detail -> same order) inside drawer navigator and all three screens are hooked up to redux store with connect() helper.
When I do navigate('ItemDetail') from ItemList, it navigates to the screen and immediately comes back to ItemList screen. I am not sure why.
Following is my navigation structure -
const AppStack = createStackNavigator(
{
Home: {
screen: HomeScreen
},
ItemList: {
screen: ItemListScreen
},
ItemDetail: {
screen: ItemDetailScreen
}
},
{
initialRouteName: 'Home'
}
);
const DrawerNav = createDrawerNavigator(
{
DrawerApp: AppStack
},
{
drawerPosition: 'right'
}
);
const SwitchStack = createSwitchNavigator(
{
Loading: Loading,
Auth: AuthStack,
App: DrawerNav
},
{
initialRouteName: 'Loading'
}
);
This is how my each navigation screen component looks -
export class ProviderListScreen extends Component {
render() {
const { navigation } = this.props;
// ItemList is hooked up to Redux via connect() helper
return <ItemList navigation={navigation} />;
}
On my ItemDetail component, I get the Item data through route params from ItemList screen and I also dispatch an action (To reset some part of the store state) in component did mount. As soon as I do that, previous screen (ItemList) is automatically rendered.
Inside item detail, I make API call to create booking for that item and the booking object is managed by redux. Once I land on the ItemDetail, I reset the booking object for new booking data.
Here is the snippet of ItemDetail's componentDidMount -
componentDidMount() {
this.props.resetItembooking();
}
I am not sure what is causing this behaviour. If I remove the ItemList screen and jump directly to ItemDetail screen from HomeScreen, this issue does not occur.
Any help is appreciated.
I had the exact same problem however I tried the answer given in the original post comments sections given by Awadhoot but this did not work for me.
For anyone still trying to solve this issue, ensure you do not have any recurring intervals setup. Therefore you should always clear intervals before navigating away:
clearInterval(this.intervalId);
In react-navigation 5 you can use the useIsFocused hook for that:
import { useIsFocused } from '#react-navigation/native';
// ...
function Profile() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}
From the docs: https://reactnavigation.org/docs/use-is-focused/

Finish current component while navigating to next component using React Native Navigation?

I wanted to close the current component completely while navigating to next component in react-native.
I am using react-navigation for navigating between screens.
Scenario is, I am having two js in my project, Login.js and Home.js. When user logs in into the app it saves the credentials in the AsyncStorage. Every-time when user comes to Login Screen it checks for whether user is logged in already or not. If the user is logged in then app will directly navigate you to the Home page, at this action I want to close the login screen completely.
Currently with my implementation the Login screen remains in to the navigation stack. When I press back from the Home page the app should be closed completely and should not relaunch with login screen again.
Here is my StackNavigator code :
const navigationStack = createStackNavigator(
{
Login: {
screen: LoginScreen
},
Home: {
screen: HomeScreen
},
},
);
For navigating :
this.props.navigation.navigate('Home');
Please let me know what I am doing wrong with my existing code?
You can implement this by multiple ways. Like using replace or reset action on stack Navigator, or using switch Navigator instead of stack Navigator.
Using Reset: (Empty stack and navigate to specified screen)
import { StackActions, NavigationActions } from 'react-navigation';
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Home' })],
});
this.props.navigation.dispatch(resetAction);
Using replace: (replace current screen with the specified screen)
this.props.navigation.replace("Home");
Using Switch Navigator:(Recommended)
const navigationStack = createSwitchNavigator(
{
Login: {
screen: LoginScreen
},
Home: {
screen: HomeScreen
},
},
);
// Navigate to screen
this.props.navigation.navigate("Home");
This can be achieved without having to add back handling code to each and every screen by modifying the getStateForAction method of the particular StackNavigator's router.
const navigationStack = createStackNavigator(
{
Login: {
screen: LoginScreen
},
Home: {
screen: HomeScreen
},
},
);
The getStateForAction method can be modified to achieve this
const defaultStackGetStateForAction =
navigationStack.router.getStateForAction;
navigationStack.router.getStateForAction = (action, state) => {
if(state.index === 0 && action.type === NavigationActions.BACK){
BackHandler.exitApp();
return null;
}
return defaultStackGetStateForAction(action, state);
};
the state.index becomes 0 only when there is one screen in the stack.
You can check with this Back Handling

React Navigation - tutorial for nesting navigators: TabNavigator overwrites StackNavigator

While working through the React Navigation tutorial on nesting navigators, I find that the tab navigator, MainScreenNavigator, overwrites the stack navigator, SimpleApp. The result is that the tabbed screens called in that object are the only ones that are displayed. The expected behavior that does not occur is that the button which links to the nested navigator never displays, so I cannot access the ChatScreen object.
const MainScreenNavigator = TabNavigator({
Recent: { screen: RecentChatsScreen },
All: { screen: AllContactsScreen },
});
I've spent hours trying to understand what I might have missed. This is my first attempt at learning this package, so I don't know if the tutorial is wrong, or that I missed a detail that breaks the process. The entire App.js file is located here.
Your help is much appreciated.
First, you need to pass StackNavigator's navigation to screens of Tabs. Because they are only screens you can deal with now since it's a initial screen.
const MainScreenNavigator = TabNavigator({
Recent: { screen: ({screenProps}) => <RecentChatsScreen screenProps={screenProps}/>,
All: { screen: ({screenProps}) => <AllContactsScreen screenProps={screenProps}/> },
});
//I added the export keyword.
export const SimpleApp = StackNavigator({
Home: {
screen: ({navigation}) => <MainScreenNavigator screenProps={{myStackNavigation:navigation}}/>,
navigationOptions: {
title: 'My Chats',
},
},
Chat: { screen: ChatScreen },
})
Now, you can call below code in RecentChatsScreen or AllContactsScreen.
this.props.navigation.screenProps.myStackNavigation.navigate('Chat');

Route to Login screen in react native in case a user is not authenticated

I've seen a lot of examples how it is done with other libraries but I'm using react-navigation one and need a solution for it.
Here is a code example:
const MainTabs = TabNavigator({
Profile: { screen: ProfileScreen },
Home: { screen: HomeScreen },
});
const RootNavigator = StackNavigator({
Main: { screen: MainTabs },
Login: { screen: LoginScreen },
},{
initialRouteName: "Login",
headerMode: "none",
});
export default RootNavigator;
Full app code you can find here: https://snack.expo.io/Hy8IXWppg
User authentication status is checked on my server.
I want Login screen to be rendered for a user on the app launch in case the user is not authenticated but ideally, it would be nice to route the user to Login in case his session is deleted/expired on the server.
In other words, I want to be able to do that at any time from any code part.
I know I can do this with code like below
if(!userIsLoggedIn()) {
this.props.navigation.dispatch(
NavigationActions.navigate({routeName: 'Login'}) );
}
inside a screen component (where userIsLoggedIn is my custom auth checking method), but I don't want to perform this action manually in each component. But even I wouldn't have another choice, where to put userIsLoggedIn() check? Inside consctructor/componentWillMount/componentWillUpdate?
UPDATE
I expected a solution which would allow me to specify all screens/routes which require authentication.
It seems to me that I have to use routes here with some custom rules.
But there are two kinds of standard routers: TabRouter and StackRouter.
I'm going to have 3-level navigation
StackNavigator
LoginScreen
RegisterScreen
AboutScreen
FirstLevelTabNavigator
Tab1: AccountScreen
Tab2: SecondLevelTabNavigator
Tab1Screen
Tab2Screen
Tab3Screen
Obviously, Login, Register and About screens don't require authentication, but on an attempt to access Account, Tab1Screen, Tab2Screen or Tab3Screen users must be redirected to LoginScreen.
Should I construct 3 routers for each navigator now?
I think you should have another screen which would be your first screen. This can be a simple component which would just be an ActivityIndicator. In that you should check with your server and navigate accordingly.
Ex.
InitialComponent
componentWillMount() {
getStatus(this.navigate.bind(this))
}
navigate(loggedIn) {
this.setState({animating: false});
if(loggedIn) {
navigate to Main
} else {
navigate to Login
}
}
render() {
return(<View> <ActivityIndicator animating={this.state.animating}/> </View>);
}
This would also have benefit of showing your user that you are processing something, before redirecting them(For example if the request takes time).
You need to use SwitchNavigator for this. The React Navigation docs explain how to do an authentication flow here:
https://reactnavigation.org/docs/en/auth-flow.html
The example below is from their docs:
import { createSwitchNavigator, createStackNavigator } from 'react-navigation';
// Implementation of HomeScreen, OtherScreen, SignInScreen, AuthLoadingScreen
// goes here.
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
);
Then you navigate doing something like this:
this.props.navigation.navigate(userToken ? 'App' : 'Auth');