Weird behaviour with Double Tapping BottomTabNavigator - React Navigation - android

I use React Navigation to do my Navigation in my mobile app and I have a structure navigation like this:
const AccountStack = createStackNavigator(
{
Account: AccountView,
...
},
{
initialRouteName: 'Account',
headerMode: 'screen',
....
}
)
const SearchUsersStack = createStackNavigator(
{
SearchUsers: SearchUsersView,
UserProfile: UserProfileView,
FriendsOfUser: FriendsOfUserView
},
{
...
}
)
const AccountModalStack = createStackNavigator(
{
AccountStack: AccountStack,
SearchUsersStack: SearchUsersStack,
},
{
initialRouteName: 'AccountStack',
headerMode: 'none',
mode: 'modal',
}
)
const MainApp = createBottomTabNavigator(
{
MainHome: HomeStack,
MainPlay: PlayStack,
MainAccount: AccountModalStack
},
{
...
}
)
If I'm in the "search User" stack (for example, in SearchUserView) and I click on the "Account" icon in the bottom tab navigator, the stack will dismiss correctly and I will return to my "account" view.
However, if I am in one of the routes of my AccountStack and I click on the "Account" icon in the bottom tab navigator, the stack does not dismiss. So if I'm very far in the account stack, I have to go back with the back arrow.
Why does it work when I'm in the SearchUserStack but not when I'm in my AccountStack ?
I hope to find help!
Thank you !
Viktor

You can override what happens when you tap on a tab, which you can do via the navigationOptions of each navigator in the tab bar.
const AccountModalStack = createStackNavigator(
{
AccountStack: AccountStack,
SearchUsersStack: SearchUsersStack,
// ...
},
{
initialRouteName: 'AccountStack',
navigationOptions: {
tabBarOnPress: ({ navigation }) => {
navigation.navigate({
routeName: 'AccountStack', // navigates to the initial route
action: navigation.popToTop(), // go to the top of the stack of that route
})
},
// ...
},
// ...
}
)
It's likely that the tab by default attempts to simply navigate to the initial route, without resetting the stack.

Related

React Navigation 3: Back button in Android doesn't back to previous screen

I am upgrading my router configuration of my React Native app using React Navigation 3 and many things has been improved now but I don't understand why when I press the back button in Android, is not sending me to the previous view and instead is sending me to the Home one.
My routes
const drawerConfig = {
initialRouteName: 'Home',
contentComponent: SideMenu,
drawerWidth: width,
}
const MainDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: Home,
},
Company: {
screen: Company,
},
Gifts: {
screen: Gifts,
},
Jobs: {
screen: Jobs,
},
Job: {
screen: Job,
},
Contact: {
screen: Contact
}
},
drawerConfig,
);
const InitialStack = createStackNavigator(
{
Menu: {
screen: Menu,
path: 'menu/',
}
},
{
initialRouteName: 'Menu',
headerMode: 'none',
}
);
const SwitchNavigator = createSwitchNavigator(
{
Init: InitialStack,
App: MainDrawerNavigator,
},
{
initialRouteName: 'Init',
}
);
const AppContainer = createAppContainer(SwitchNavigator);
export default AppContainer;
If I do this: Open Drawer, open Jobs then press in a job button to load the Job view the flow is working well but If I press the back button in the Job view is not showing the Jobs but the Home.
I am navigation using this.props.navigation.navigate('...') because the push is not working.
Do you know why?
I am using react-navigation 3.5.1 and react-native 0.59.3
I just figured out.
I was not writing the right configuration for my routes because if I wanted to back from Job to Jobs instead to Home, a stack was necessary for every "stack" of views I needed, so now they can work exactly how I want, the pop is working great without using the back handler event.
Like this:
// Jobs stack
const JobsStack = createStackNavigator(
{
JobList: {
screen: Jobs,
},
Job: {
screen: Job,
},
},
{
headerMode: 'none',
}
);
// Main drawer
const MainDrawerNavigator = createDrawerNavigator(
{
...
Jobs: JobsStack,
Contact: {
screen: Contact
}
...
},
drawerConfig,
);
Thank you to all the people that helped me :)
You can import BackHandler from 'react-native' and can use the code like below in your screen class from where you want to go back:
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = () => {
this.props.navigation.goBack();
return true;
};

Prevent user from returning to previous screen StackNavigator

I want to do a simple task: after a successful login redirect the user to his home page. I use react-navigation's StackNavigator for that purpose:
// App.js
class App extends Component {
render() {
return (<RootStack />);
}
}
const RootStack = createStackNavigator(
{
Login: { screen: Login, navigationOptions: { header: null }},
Home: { screen: Home, navigationOptions: { header: null }}
},
{
initialRouteName: 'Root'
}
)
How do I prevent user from returning to Login screen after login? To prevent use back button in android, I would use this one:
// Home.js
import React, { Component} from 'react';
import { ... , BackHandler } from 'react-native';
class Home extends Component {
constructor(props) {
super(props);
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid);
}
onBackButtonPressAndroid = () => {
return true;
}
}
But doing this way, I disable back button at all. Is there another way to achieve the goal?
The react native documentation has an excellent page on how to make an authentication flow.
You may not be familiar with SwitchNavigator yet. The purpose of SwitchNavigator is to only ever show one screen at a time. By default, it does not handle back actions and it resets routes to their default state when you switch away. This is the exact behavior that we want from the authentication flow: when users sign in, we want to throw away the state of the authentication flow and unmount all of the screens, and when we press the hardware back button we expect to not be able to go back to the authentication flow. We switch between routes in the SwitchNavigator by using the navigate action. You can read more about the SwitchNavigator in the API reference.
According to the documentation, this SwitchNavigator is implemented like this:
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',
}
);
So, to achieve what you are looking for, you would want to change your RootStack to the following (note i have not tested this code):
const RootStack = createSwitchNavigator(
{
Loading: ,//put your loading screen here
Auth: { screen: Login, navigationOptions: { header: null }},
App: { screen: Home, navigationOptions: { header: null }}
},
{
initialRouteName: 'Loading'
}
)
Then, in your loading screen, you would fetch whatever state is needed to determine if the user is already signed in and you would call either this.props.navigation.navigate('App'); to skip the login screen and take the user directly to your app, or this.props.navigation.navigate('Auth'); to send your user to the login page. the SwitchNavigator automatically handles disabling back navigation for you.

React Native Navigation

I am making an app , in which I used Drawer Navigator and i nested a Stack Navigator inside it . Drawer contains screen HOME , PRODUCTS , Profile etc.
While i used Stack navigator inside Home screen to switch to 3 different screen
Products->ItemDescription .
Issue 1: If I go to Product or Item Description via Home .And if i open drawer in products or ItemDescription , I cant go back to home on clicking HOME from drawer .WHile clicking other options on drawer menu i can switch to diff screens.
Issue 2 : I have a Navbar from 'navbar-native' , which i used in every component namely HOME , PRODUCTS , ItemDescription , Cart etc. Can you help me to link with redux so that i can open different screen on clicking on its ICON namely CART and SEARCH screen.
Drawer Code :
const RootDrawer = DrawerNavigator({
Home: {
screen: HomeScreen,
style :{backgroundColor:'blue'},
},
Profile: {
screen: ProfileScreen,
},
Products: {
screen: Products,
},
Cart: {
screen: Cart,
},
});
export default RootDrawer;
HomeScreenCode :
class Home extends React.Component {
constructor(props){
super(props) ;
this.openUp = this.openUp.bind(this)
}
openUp(){
this.props.navigation.navigate('DrawerOpen');
// open drawer passed to all components to open Drawer from hamburger
//iconpresent at NAVbar
}
render() {
return (
<SimpleApp key={1} screenProps={this.openUp} />
);
}
}
const SimpleApp = StackNavigator(
{
drwlyout: { screen: DrawerLayoutMain , navigationOptions:({navigation}) => ({ header: false } ) },
prodlyout: { screen: Products , navigationOptions:({navigation}) => ({ header: false })},
itemdsclyout: { screen: ItemDescription , navigationOptions:({navigation}) => ({ header: false })},
navbarlayout: { screen: NavBarComponent , navigationOptions:({navigation}) => ({ header: false })},
} );
function mapDispatchtoProps(dispatch){
return bindActionCreators({getDataFirebase:getDataFirebase},dispatch);
}
export default connect(null,mapDispatchtoProps)(Home);
For your first issue, you will need a navigation reducer, a basic description of how to implement Redux with React-Navigation can be found here
When you have to listen for the specific navigation action inside of your reducer. For example, your DrawerNavigator looks something like this:
const nav = (state = INITIAL_NAV_STATE, action) => {
let nextState;
switch (action.type) {
case REHYDRATE:
// if you're using redux-persist
case 'Main': // where 'Main' is your StackNavigator
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Home' }),
state
);
break;
See the 'routeName' which is the name of your Home screen. So, your root or Main drawer Navigator should look something like this:
const routeConfig = {
Main: {
screen: MainStackNavigator,
navigationOptions: {
drawerIcon: ({ tintColor }) => (
<Icon name="a006_start" size={28} color={tintColor} />
),
drawerLockMode: 'locked-closed'
}
},
.... additional Code ....
.... like navigationOptions etc. ....
const MainDrawerNavigator = DrawerNavigator(routeConfig, routeOptions);
export default MainDrawerNavigator;
I'm sorry that I haven't worked with 'navbar-native' yet but I guess that if you're wiring up your redux configuration like this you could listen to the particular navigation actions. Which then can be processed inside of your navigation reducer.

Navigating between components reloads all components in navigator routes

I've read somewhere that when you use any navigator from the react-navigation package and if you implement redux; every component in the navigators' routes will be reloaded (i.e componentWillRecieveProps)
However, I've got two pages where the user can login
export const MainScreenTabNavigator = TabNavigator({
Start: {
screen: Start,
navigationOptions: {
title: 'Start',
},
},
},{
tabBarPosition: "bottom",
tabBarOptions: {
activeTintColor: '#222',
labelStyle: {
color: '#ddd',
},
style: { backgroundColor: '#333' },
}
});
export const AppNavigator = StackNavigator({
Main: {
screen: MainScreenTabNavigator, // Nested tab navigator
},
Login: {
screen: Login,
navigationOptions: {
title: 'Aanmelden',
}
},
Camera: {
screen: AppCamera,
navigationOptions: {
title: 'Camera',
}
}
}, {
mode: 'modal',
headerMode: 'none',
});
The login screen is initially shown to the user. It has a form where the user can enter it's credentials manually and a button that navigates to the camera where the user can scan a QR code with login credentials.
In both cases the user dispatches a login action.
Both the login page and the camera page listen to the same prop changes:
componentWillReceiveProps(nextProps) {
if (nextProps.account.loginSuccess) {
this.props.navigation.dispatch(NavigationActions.navigate({ routeName: 'Start' }));
} else {
if (nextProps.account.loginError) {
Toast.showLongBottom(nextProps.loginError);
}
}
this.setState({spinnerVisible: false});
}
The app successfully navigates to 'Start' but when it does both the Login and Camera page are reloaded, causing an infinite loop into componentWillReceiveProps and infinitely navigates to 'Start' over and over again.
This is my navigation reducer:
function nav(state = initialNavState, action) {
const nextState = AppNavigator.router.getStateForAction(action, state);
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
}
What can I do to prevent this behavior?
Well,
As a temporary solution I introduced another boolean into the nav state:
function nav(state = initialNavState, action) {
let nextState = null;
if (action.type === 'Navigation/NAVIGATE' && action.routeName === 'Start') {
nextState = {...AppNavigator.router.getStateForAction(action, state), authenticated: true};
} else {
nextState = AppNavigator.router.getStateForAction(action, state);
}
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
}
I use authenticated to check if the login or camera component should navigate to start after logging in.
It works but it still feels like I'm missing something.

React Navigation + Redux close App on Back Pressed

After converting the app to redux, my react-navigation got some problem. Previously, before integrating with redux, when I press back button (Physical button) react-navigation back to the previous screen. After integrating with redux, the back button will close the app. But, it's still working with goBack() function.
I'm following the guide: https://reactnavigation.org/docs/guides/redux
And read some code from here : https://github.com/react-community/react-navigation/tree/master/examples/ReduxExample
And, this is my Navigator configuration
export const AppNavigator = StackNavigator(
{
Home: { screen: HomeScreen },
ChatDetail: { screen: ChatDetail },
PulsaDetail: { screen: PulsaDetailScreen },
Pulsa: { screen: Pulsa }
},
{
headerMode: 'none',
}
)
class AppWithNavigation extends Component {
render(){
return(
<AppNavigator navigation={ addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})} />
)
}
}
const mapStateToProps = (state) => ({
nav: state.nav
})
export default connect(mapStateToProps)(AppWithNavigation)
EDIT: It's can be done with manual handle & dispatch back action, but it's can't do it automaticlly? just like before using redux?
BackHandler.addEventListener('hardwareBackPress',() => {
this.props.goBack()
return true
})
After post Github issue in react-navigation repository, I got the answer.
Should add manually the back listener on top of screen / component
// App.js
import { BackAndroid } from 'react-native'
// [...]
componentDidMount() {
BackAndroid.addEventListener('backPress', () => {
const { dispatch, nav } = this.props
if (shouldCloseApp(nav)) return false
dispatch({ type: 'Back' })
return true
})
}
componentWillUnmount() {
BackAndroid.removeEventListener('backPress')
}
// [...]
https://github.com/react-community/react-navigation/issues/2117
https://github.com/react-community/react-navigation/issues/117
UPDATE:
https://facebook.github.io/react-native/docs/backhandler

Categories

Resources