Home > Software engineering >  Force the screen defined in initialRouteName to be displayed when the tab button is pressed
Force the screen defined in initialRouteName to be displayed when the tab button is pressed

Time:06-09

I have a problem with bottom tab navigation. This is screen structure:

  • Main Tab:
    • Tab A
      • Screen A1
    • Tab B
      • Screen B1
      • Screen B2

I can navigate from Screen A1 to Screen B2 (changing tab). I can navigate from Screen B1 to Screen B2.

This is the example code:

import React from "react";
import { StyleSheet, View, Text, Button } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";

const Tab = createBottomTabNavigator();
const Stack = createStackNavigator();

const TabAScreen1 = ({ route, navigation }) => {
  return (
    <View>
      <Text>Tab A screen 1</Text>
      <Button
        title="Go to tab B screen 2"
        onPress={() => {
          navigation.navigate("TabBStack", { screen: "TabBScreen2" });
        }}
      />
    </View>
  );
};
const TabBScreen1 = ({ route, navigation }) => {
  return (
    <View>
      <Text>Tab B screen 1</Text>
      <Button
        title="Go to tab B screen 2"
        onPress={() => {
          navigation.navigate("TabBScreen2");
        }}
      />
    </View>
  );
};
const TabBScreen2 = ({ route, navigation }) => {
  return (
    <View>
      <Text>Tab B screen 2</Text>
    </View>
  );
};
function TabBStack() {
  return (
    <Stack.Navigator initialRouteName="TabBScreen1" screenOptions={{ headerShown: false }}>
      <Stack.Screen name="TabBScreen1" component={TabBScreen1}/>
      <Stack.Screen name="TabBScreen2" component={TabBScreen2}/>
    </Stack.Navigator>
  );
}
function MainTab() {
  return (
    <Tab.Navigator screenOptions={{ headerShown: false }}>
      <Tab.Screen name="TabAScreen1" component={TabAScreen1}/>
      <Tab.Screen name="TabBStack" component={TabBStack}/>
    </Tab.Navigator>
  );
};

const App = props => {
  const navigationRef = useRef();

  return (
    <NavigationContainer>
      <MainTab/>
    </NavigationContainer>
  );
};

const styles = StyleSheet.create({});

export default App;

Navigation packages:

"@react-navigation/bottom-tabs": "^6.2.0",
"@react-navigation/native": "^6.0.8",
"@react-navigation/stack": "^6.1.1",

At the beginning, if I navigate to Tab B using bottom tab button, Screen B1 is loaded. Then I can navigate to Screen B2 from Screen B1 or Screen A1. Being in Screen B2, if I tap on Tab B bottom tab button the Screen B2 is popped and Screen B1 is showed. This is the expected behavior.

The problem is this one. At the beginning, if I navigate to Screen B2 from Screen A1, Screen B2 is loaded. At this point, I can't navigate to screen B1 at all. If I tap on Tab B bottom tab button nothing happens, even initialRouteName of TabBStack is set to TabBScreen1. I think in this case the first screen in the stack is B2.

Is it possible to pop B2 (or all screens in the stack) and push B1 tapping bottom tab button? I know about listeners, but I don't know how to implement this behavior.

function MainTab() {
  return (
    <Tab.Navigator screenOptions={{ headerShown: false }}>
      <Tab.Screen name="TabAScreen1" component={TabAScreen1}/>
      <Tab.Screen name="TabBStack" component={TabBStack}
        listeners={({ navigation }) => ({
          tabPress: (e) => {
            console.log("Tab B pressed");
            // If current route is not Screen B1, then pop all and push Screen B1
          },
        })}
      />
    </Tab.Navigator>
  );
};

CodePudding user response:

This only happens if you initially load the application start in stack A and have never been to stack B before. This causes the initial route of the TabBStack to not be mounted!

If you then navigate to a nested screen inside the stack of the TabBStack from TabAScreen1, the default route reset functionality does not work. The framework does only mount TabBScreen2 but not the initial route specified in that particular tab.

If we do the following instead:

  1. Load the application: the default screen is TabAScreen1
  2. Go to TabBStack using the bottom tab navigation.
  3. Go back to TabAScreen1.
  4. Click the Go to tab B screen 2 button
  5. Press the TabBStack bottom tab button

You will notice that it now successfully resets the route. This is the default behavior used in most applications.

How do we solve this?

We have luck. The framework allows us to do this in a very convenient way by using the initial prop.

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.

In our case, this causes the TabBScreen1 to never be mounted. Thus, we can not reset to this route. If we provide initial: false while navigating to the nested screen, the navigation framework will not use TabBScreen2 as the initial route but TabBScreen1. If we do this, we can reset the route by pressing the TabBStack button again.

navigation.navigate("TabBStack", { screen: "TabBScreen2", initial: false });
  • Related