Wrong back button behavior in react native - javascript

guys I have a question about navigation in react native.
So I mainly use TabNavigator. I have 2 main stack navigators in the app
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeStackScreen} />
<Tab.Screen name="Profile" component={ProfileStackScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
In my ProfileStack screen, I have also two pages: MyProfile and UsersProfile:
const ProfileStack = createNativeStackNavigator();
function ProfileStackScreen({route, navigation}) {
return (
<ProfileStack.Navigator initialRouteName="MyProfile">
<ProfileStack.Screen name="MyProfile" component={MyProfilePage} />
<ProfileStack.Screen name="UserProfile" component={UserProfilePage} options={{
headerLeft: () => (<View>
<Button title="back" onPress={() => {navigation.goBack()}}/>
</View>)
}}/>
</ProfileStack.Navigator>
);
}
Now I want to navigate from the HomeScreen to the UserProfilePage and pass params to this screen. I'm doing it like this:
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home screen this</Text>
<Button
title="Go to another user profile"
onPress={() => navigation.navigate('Profile', {screen: 'UserProfile', params: {userId: 1235}})}
/>
</View>
);
}
So now, when I come to the page UserProfile I see that it loads also the Profile page, and just for a second I see blinking of ProfilePage and its UI that is not cool, and should it be like so? I guess not.
Then if I press the BACK button on UsersProfilePage, I'm navigating back to HomeScreen - and this is ok! This is what I expect!
But now, If I will press ProfileTab I see only UsersProfilePage but not MyProfilePage. When I press the BACK button again, I go back to the HomeScreen that is weird for me. Can u explain why it happens? Why I don't get back to the MyProfilePage.
I prepared an expo snack here. You can reproduce this behavior.

This is because you’re navigating to a screen in a nested navigator. It is ignoring the initial route. Then when you press the tab again it is still mounted and will still have the previous route state, which is just the screen without the initial screen.
By default, when you navigate a screen in the nested navigator, the specified screen is used as the initial screen and the initial route prop on the navigator is ignored. This behaviour is different from the React Navigation 4.
If you need to render the initial route specified in the navigator, you can disable the behaviour of using the specified screen as the initial screen by setting initial: false:
navigation.navigate('Root', {
screen: 'Settings',
initial: false,
});
See https://reactnavigation.org/docs/nesting-navigators/#rendering-initial-route-defined-in-the-navigator and https://reactnavigation.org/docs/navigation-lifecycle/

Related

React native clear Stack Navigator stack in bottom tab bars

I am developing an app which has bottom tab navigation. For implementing this , I've used react-navigation-material-bottom-tabs, which is working perfectly fine. Like I have 3 screens ,say Home, Profile and About in the bottom tab navigator. But in the Home screen I have multiple screens flow to be implemented. For that I used Stack Navigator, which is also working fine. So my app flow is like Home-> Screen1-> Screen2-> Screen3 Where I'm facing problem is that suppose I'm on Screen3 and then I switch to Profile screen from bottom navigation, and then again switch to Home screen.
I should be able to see Home Screen there but currently it shows Screen3
Following is my code:
MainTabs.js
import { createStackNavigator } from '#react-navigation/stack';
import Icon from 'react-native-vector-icons/Ionicons';
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs';
const HomeStack = createStackNavigator();
const ProfileStack = createStackNavigator();
const AboutStack = createStackNavigator();
const HomeStackScreen = ({ navigation }) => {
return (
<HomeStack.Navigator screenOptions={{headerShown: false, initialRouteName: 'Screen1'}}>
<HomeStack.Screen name="Screen1" component={Screen1} />
<HomeStack.Screen name="Screen2" component={Screen2} />
<HomeStack.Screen name="Screen3" component={Screen3}/>
</HomeStack.Navigator>
)
}
const ProfileStackScreen = ({ navigation }) => {
return (
<ProfileStack.Navigator screenOptions={{headerShown: false, initialRouteName: 'Profile'}}>
<ProfileStack.Screen name="Profile" component={Profile} />
</ProfileStack.Navigator>
)
}
const AboutStackScreen = ({ navigation }) => {
return (
<AboutStack.Navigator screenOptions={{headerShown: false, initialRouteName: 'About'}}>
<AboutStack.Screen name="About" component={About} />
</AboutStack.Navigator>
)
}
const MainTabScreen = () => {
return (
<Tab.Navigator
initialRouteName="Home"
activeColor="#fff"
barStyle={{ backgroundColor: 'red' }}
labeled={false}
>
<Tab.Screen
name="Home"
component={HomeStackScreen}
/>
<Tab.Screen
name="Profile"
component={ProfileStackScreen}
/>
<Tab.Screen
name="About"
component={AboutStackScreen}
/>
</Tab.Navigator>
);
export default MainTabScreen;
Use navigation.navigate('routeName') when you tap on your bottom tab navigator element. You have to register a route to the first screen of the stack, and from there you can go inside the stack as you like.
set unmountOnBlur options to true.
If you don't mind this.
Normally, we don't recommend enabling this prop as users don't expect their navigation history to be lost when switching tabs. If you enable this prop, please consider if this will actually provide a better experience for the user.
Another way is to pop the stack navigator when leaving screen3.

React Native: Tab and Stack Navigation Integrited

I have an issue with Tab Navigation and Stack.
The following is Stack and Tab Navigation:
export function MyTabs() {
return (
<Tab.Navigator
initialRouteName="Home"
activeColor={COLORS.white}
barStyle={{ backgroundColor: COLORS.mediumgrey }}
inactiveColor={COLORS.grey}>
...
<Tab.Screen
name="More"
component={MoreStack}
options={{
tabBarLabel: <Text style={styles.bottomNavBarTextSize}>More</Text>,
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons name="settings" color={color} size={26} />
)
}
} />
The component "MoreStack" is a stack Navigation which is the following:
function MoreStack() {
return (
<Stack.Navigator
initialRouteName="More"
screenOptions={{
headerStyle: { backgroundColor: COLORS.mediumgrey },
headerTintColor: COLORS.white,
headerTitleStyle: styles.navBarTitleFont
}}>
<Stack.Screen name="More" component={MoreScreen} options={{ headerShown: false }} />
...
<Stack.Screen name="Login" component={Login} options={{ headerShown: false }} />
</Stack.Navigator>
);
}
In MoreScreen Page, I have the following logout code:
logOut = () => {
firebase.auth().signOut().then(() => {
this.props.navigation.replace('Login')
});
}
The issue I have is that it does go to the login page but the bottom navigation bar does NOT go away. And if I want to go back, then it goes back to the MoreScreen which it shouldn't. Logically, once you logout, you should not be able to go back.
The following picture shows the issue
Notice how the bottom navigation bar is still there and if the back button is clicked, it goes back to the previous screen
****UPDATE
FIXED: I fixed it by combining all the stacks into 1. It would not work if you want to do STACK1>MyTabs>STACK2. Fixed it by doing STACK1>MyTabs>Stack1.
Think about it this way, you have a Tab Navigation, Stack navigation and a Login Screen. Your parental status of your navigations is Tab Nav > Stack Nav > Login Screen. So when you are in any screen in your Stack Nav, you are going to be seeing the Tabs. What you need to do is the opposite.
Put your Tab Nav into a Stack navigator and name it App Navigator. And create another Stack Nav and name it CredentialNavigator, and put your Login/Signup screen into it. And in your Main/Root navigation file, you can dynamically render either your CredentialNavigator or AppNavigator based on your Login State.
isLogged ? false return <CredentialNavigator/> : return <AppNavigator>
This is very simple of course, but you can definitely expand this tree. I suggest you to go through almost all React Navigation 5 docs, it is very easy to follow and very informative.

How to reset react navigation when "unmounting" from a tab?

so I have a stack named AuthStack like so
const AuthStack = ({ resetPassword, updateEmail }: any) => (
<Stack.Navigator
screenOptions={{
cardStyle: { backgroundColor: '#F2F1F7' },
headerShown: true,
headerTitle: '',
}}
>
{resetPassword ? (
<Stack.Screen name="Reset Password">{(props: any) => <ResetPassword />}</Stack.Screen>
) : updateEmail ? (
<Stack.Screen name="Update Email">{(props: any) => <UpdateEmail />}</Stack.Screen>
) : (
<Stack.Screen name="Home">{(props: any) => <Home />}</Stack.Screen>
)}
</Stack.Navigator>
)
would be good to know if this is the right way to do it. but essentially from my account page I have links to reset password and update email. I put both these in the AuthStack as I wasn't sure if this was the best place. anyway this sort of works, however if I click on "update email" and get navigated to there, if I then decide actually I want to go to another page and navigate away, if I then navigate back by clicking on "Home", it takes me to the update email page still. even though I really want to show the home screen at this point. also if I click on home whilst it's active it doesn't take me there. thus meaning I can never see my home page again.
so I was wondering if there is some sort of listener that when I navigate away from this page it will alway show me Home by default?
any ideas? and I don't want to show reset password /update email as there own tabs hence hiding them under home. is there a better approach for this?
I really don't know why you are conditionally render the screens, I think you are using name in bad way. You should navigate like navigation.navigate('ForgotPassowrd') (name of the screen). It keep showing you update email because Home not exist.
export const AuthNavigator = () => {
return (
<Stack.Navigator headerMode='none'>
<Stack.Screen name='Home' component={SignupContainer}></Stack.Screen>
<Stack.Screen name='ForgotPassword' component={ForgotPasswordContainer}></Stack.Screen>
<Stack.Screen name='Update Email' component={UpdateEmailContainer}></Stack.Screen>
</Stack.Navigator>
);
};

How to clean up state in class component in React Native?

I'm a newbie in React Native and struggling in cleaning up the state of the screen.
Like screen A has some states --> screen B, back to screen A, old states are clear. I'm using React Navigation V5
What I'm trying to do is:
After navigating from MainMap.js to the last screen TripsListScreen.js (the whole process is a Stack of 4 screens, nested in a Drawer), I got all the data stored in Redux's store, and display it in TripsListScreen.
The problem is when I press the add button in TripsListScreen to comeback at the MainMap.js, it doesn't clean up every state as I expect.
Here's the MainMap.js 's states:
const initialState = {
hasMapPermission: false,
_userLocationDisplayed: null,
userLatitude: 0,
userLongitude: 0,
initial_UserLatitude: 0,
initial_UserLongitude: 0,
userLocationAddress: '',
destination: [],
destinationName: [],
destinationAddress: [],
destinationCoords: [],
destinationImageUri: [],
numOfInput:[0,1],
counter: 1,
wayPoints: [],
markers: [],
}
class MainMap extends React.Component{
constructor(props){
super(props);
this.state = initialState;
};
componentDidMount(){
console.log('Hello',this.props)
if(this.props.route.params === true){
this.setState(initialState)
}
this._requestUserLocation();
};
Basically, I tried to pass a boolean param from TripsListScreen to MainMap, and if the param is true, I'll set all the states back to the beginning. However, it doesn't work as expected.
Here's TripsListScreen:
//...
<Layout style={styles.bottom}>
<TouchableOpacity onPress={() => props.navigation.navigate('PlanningProcess', {
screen: 'MainMapScreen',
params: {doAddPlace: true}
})} style={styles.createTrip}>
<Layout style={styles.button} >
<Icon name='add' size={35} />
</Layout>
</TouchableOpacity>
</Layout>
//...
Here's the Navigation:
StackNavigators:
const Stack = createStackNavigator();
const StackNavigator = (props) => {
return(
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name='MainMapScreen' component={MainMap} />
<Stack.Screen name='TripDescription' component={TripDescription} />
<Stack.Screen name='TripsListDetailScreen' component={TripsListDetailScreen} />
<Stack.Screen
name='TripsListScreen'
component={TripsListScreen}
options={{
headerLeft: () => (<Icon style={{marginHorizontal: 30, marginTop: 30}} color='white' name='menu' size={30} onPress={() => props.navigation.dispatch(DrawerActions.openDrawer())}/>),
title:'Something'
}}
/>
</Stack.Navigator>
);
};
export default StackNavigator;
Main Navigators:
const Navigator = () => {
return(
<NavigationContainer>
<Drawer.Navigator
statusBarAnimation='slide'
drawerContent={props =>
<DrawerContent {...props} />}>
<Drawer.Screen name='Welcome' component={WelcomeStackScreen} />
<Drawer.Screen name='TripsListScreen' component={TripsListScreen} />
<Drawer.Screen name='PlanningProcess' component={StackNavigators} />
</Drawer.Navigator>
</NavigationContainer>
);
};
export default Navigator;
This is what MainMap renders:
This is what I expected, when navigating from TripsListScreen ( to create a new trip):
PLEASE HELP !!!
ComponentDidMount in MainMap.js doesn't get triggered because the screen is mounted already. Please look at this `componentDidMount()` function is not called after navigation
The method ComponentDidMount() only triggers for the first time at the mounting of the component and as you are navigating to a new screen, the previous screen is still mounted in the stack.
If you want to re-initialize your state every time your component gets the focus, you can set a listener on the focus on the navigation.
Like this,
const unsubscribe = navigation.addListener('willFocus', () => {
// Do something
// re-initialise the state
});
in StackNavigator, screens don't unmount when you open new screens on top of them. So if you go from A to B, then from B to C, both A and B will stay mounted. If you go back from C to B, C will unmount. It's like push and pop methods on array. componentDidMount in MainMap is not being called when you go back to it, as it doesn't unmount in first place. It is explained here Navigation Lifecycle.
As you are using Redux and saying that all the data is stored in Redux store, make your MainMap component render solely from the data from store, not from own state. You can then manipulate this data from TripsListScreen by dispatching actions. The easiest would be creating something like RESET_MAIN_MAP action that will reset that part of the state for MainMap screen

Navigate to URL/Deep Link with DrawerNavigator [React native]

How can we open a link from a navigator entry? For example, like:
const Home = DrawerNavigator ({
Account: { screen: Account },
Availability: { screen: Availability },
Favorites: { screen: Favorites },
Website: { screen: Linking.openURL('http://www.example.com') }, }
I know this was asked a while ago, but I've recently come across this need and found a working solution.
You want to first establish the drawer with createDrawerNavigator:
import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList, DrawerItem } from '#react-navigation/drawer';
import { Linking } from 'react-native';
const Drawer = createDrawerNavigator();
With the latest version of react-native navigation, we can't pass fields to DrawerNavigator like you're doing. One way to set up the drawer and its navigation contents is like so:
return (
<Drawer.Navigator
initialRouteName="Account"
drawerPosition="right"
drawerContentOptions={{
activeTintColor: 'white',
inactiveTintColor: 'blue',
activeBackgroundColor: 'blue',
labelStyle: {
fontFamily: 'Arial',
fontSize: 18,
textTransform: 'uppercase',
paddingTop: 5
}
}}
drawerContent={props => <CustomDrawerContent {...props}/>}
>
<Drawer.Screen name="Account" component={Account} />
<Drawer.Screen name="Availability" component={Availability} />
<Drawer.Screen name="Favorites" component={Favorites} />
{/* Custom Links (defined next) */}
</Drawer.Navigator>
);
I've included my return statement to show that this is what I'm rendering on the screen. drawerContentOptions shows one of the parameters you can pass to the drawer. All options are defined here:
https://reactnavigation.org/docs/drawer-navigator/
Next, we want to create the custom drawer content that we referenced in one of the Drawer.Navigator props. You can also apply very similar props like we did earlier to the drawer navigation items. Keep in mind that all custom links are going to display below the Drawer.Screen components referenced/defined earlier. This is due to the line DrawerItemList {...props}> which takes in the defined list of navigation links and displays them before our custom links. You can reverse this so custom displays first, but I'm not sure if you can put custom links in the middle.
// set up custom links for main navigation drawer
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem
label="Website"
inactiveTintColor={'blue'}
labelStyle= {{
fontFamily: 'Arial',
fontSize: 18,
textTransform: 'uppercase',
paddingTop: 5
}}
onPress={() => Linking.openURL('http://www.example.com')}
/>
</DrawerContentScrollView>
);
}

Categories