react-nativereact-navigationreact-navigation-v6

"Found screens with the same name nested inside one another" after upgrade to react-navigation v6


I've seen other people with the same issue but I can't get my code to work. After upgrading from RN 5 to RN 6, I get the error below.

Found screens with the same name nested inside one another. Check:

AppSideDrawerScreen > HomeSide > HomeBottom, AppSideDrawerScreen > HomeSide > HomeBottom > HomeBottom

Everything worked fine with RN5, but my app crashes since the upgrade. I can access the Home screen from the drawer menu and also bottomTab.

Navigation.js:

import { Entypo } from "@expo/vector-icons";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { createDrawerNavigator } from "@react-navigation/drawer";
import { NavigationContainer, useTheme } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import React, { useCallback, useMemo, useState } from "react";
import { TouchableOpacity } from "react-native";
import FlashMessage from "react-native-flash-message";

import AddGroup from "../screens/AddGroup";
import GroupList from "../screens/GroupList";
import Groups from "../screens/Groups";
import History from "../screens/History";
import Home from "../screens/Home";
import Loading from "../screens/Loading";
import Settings from "../screens/Settings";
import { MyDarkTheme, MyLightTheme } from "../styles/themes";
import { PreferencesContext } from "../util/PreferencesContext";

// Header style to be used on each (normal) screen
const screenOptionWithHeaderStyle = {
  
  presentation: "modal" // For new SDK 46

};

// Header style to be used on modal screens with transparent background and no header
const modalOptionStyle = {
  headerShown: false,
  cardStyle: { backgroundColor: "transparent" },
  cardOverlayEnabled: true,
  cardStyleInterpolator: ({ current: { progress } }) => ({
    cardStyle: {
      opacity: progress.interpolate({
        inputRange: [0, 0.5, 0.9, 1],
        outputRange: [0, 0.25, 0.7, 1],
      }),
    },
    overlayStyle: {
      opacity: progress.interpolate({
        inputRange: [0, 1],
        outputRange: [0, 0.8],
        extrapolate: "clamp",
      }),
    },
  }),
};

/**
 * Component: OpenDrawerIcon
 * Icon on left side to open Drawer menu. Located in headerLeft
 * @param {*} navigation  Navigation object which can then be passed to other functions and components to navigate
 */
const OpenDrawerIcon = ({ navigation }) => {
  return {
    headerLeft: () => (
      <TouchableOpacity onPress={() => navigation.openDrawer()}>
        <Entypo
          name="menu"
          size={32}
          style={{ padding: 5 }}
        />
      </TouchableOpacity>
    ),
  };
};

const HomeStack = createStackNavigator();
/**
 * Component: HomeStackScreen
 * Home screen container
 * @param {*} route  Route information used to retrieve current component name
 */
const HomeStackScreen = ({ route }) => (
  <HomeStack.Navigator screenOptions={screenOptionWithHeaderStyle}>
    <HomeStack.Screen
      name={route.name}
      component={Home}
      options={OpenDrawerIcon}
    />
  </HomeStack.Navigator>
);

const HistoryStack = createStackNavigator();
/**
 * Component: HistoryStackScreen
 * History screen container
 * @param {*} route  Route information used to retrieve current component name
 */
const HistoryStackScreen = ({ route }) => (
  <HistoryStack.Navigator screenOptions={screenOptionWithHeaderStyle}>
    <HistoryStack.Screen
      name={route.name}
      component={History}
      options={OpenDrawerIcon}
    />
  </HistoryStack.Navigator>
);

const GroupsStack = createStackNavigator();
/**
 * Component: GroupsStackScreen
 * Groups screen container
 * @param {*} route  Route information used to retrieve current component name
 */
const GroupsStackScreen = ({ route }) => (
  <GroupsStack.Navigator screenOptions={screenOptionWithHeaderStyle}>
    <GroupsStack.Screen
      name={route.name}
      component={Groups}
      options={OpenDrawerIcon}
    />
  </GroupsStack.Navigator>
);

const SettingsStack = createStackNavigator();
/**
 * Component: SettingsStackScreen
 * Settings screen container
 * @param {*} route  Route information used to retrieve current component name
 */
const SettingsStackScreen = ({ route }) => (
  <SettingsStack.Navigator screenOptions={screenOptionWithHeaderStyle}>
    <SettingsStack.Screen
      name={route.name}
      component={Settings}
      options={OpenDrawerIcon}
    />
  </SettingsStack.Navigator>
);

const AppBottomTabs = createBottomTabNavigator();
/**
 * Component: BottomTabsScreen
 * Bottom tab elements containing the different screens containers, also accessible from Drawer menu
 */
const BottomTabsScreen = () => {

  return (
    <AppBottomTabs.Navigator>
      <AppBottomTabs.Screen
        name="HomeBottom"
        component={HomeStackScreen}
        options={{
          tabBarIcon: (props) => (
            <Entypo name="home" size={props.size} color={props.color} />
          ),
        }}
      />
      <AppBottomTabs.Screen
        name="History"
        component={HistoryStackScreen}
        options={{
          tabBarIcon: (props) => (
            <Entypo name="list" size={props.size} color={props.color} />
          ),
        }}
      />
      <AppBottomTabs.Screen
        name="Groups"
        component={GroupsStackScreen}
        options={{
          tabBarIcon: (props) => (
            <Entypo name="folder" size={props.size} color={props.color} />
          ),
        }}
      />
    </AppBottomTabs.Navigator>
  );
};

const AppSideDrawer = createDrawerNavigator();
/**
 * Component: AppSideDrawerScreen
 * Drawer element containing access to all screens in the app
 */
const AppSideDrawerScreen = () => {
  return (
    <AppSideDrawer.Navigator screenOptions={{ headerShown: false }}>
      <AppSideDrawer.Screen name="HomeSide" component={BottomTabsScreen} />

      <AppSideDrawer.Screen name="History" component={HistoryStackScreen} />

      <AppSideDrawer.Screen name="Groups" component={GroupsStackScreen} />

      <AppSideDrawer.Screen name="Settings" component={SettingsStackScreen} />
    </AppSideDrawer.Navigator>
  );
};

// Root stack container
const RootStack = createStackNavigator();
const RootStackScreen = () => {
  const [isLoading] = React.useState(false);

  return (
    <RootStack.Navigator
      screenOptions={screenOptionWithHeaderStyle}
    >
      {isLoading ? (
        <RootStack.Screen name="Loading" component={Loading} />
      ) : (
        <RootStack.Screen
          name="AppSideDrawerScreen"
          component={AppSideDrawerScreen}
          options={{ headerShown: false }}
        />
      )}
      <RootStack.Screen
        name="AddGroup"
        component={AddGroup}
        options={modalOptionStyle}
      />

      <RootStack.Screen
        name="GroupList"
        component={GroupList}
        options={({ navigation }) => ({
          animationEnabled: true,
          headerTitle: "Select an existing group",
          headerLeft: () => (
            <TouchableOpacity onPress={() => navigation.goBack()}>
              <Entypo
                name="back"
                size={32}
                color="black"
                style={{ padding: 5 }}
              />
            </TouchableOpacity>
          ),
        })}
      />
    </RootStack.Navigator>
  );
};

export default () => {
  // Default theme is dark
  const [isThemeDark, setIsThemeDark] = useState(true);
  const theme = isThemeDark ? MyDarkTheme : MyLightTheme;

  const toggleTheme = useCallback(() => {
    return setIsThemeDark(!isThemeDark);
  }, [isThemeDark]);

  // To remember preferences accross the app
  const preferences = useMemo(
    () => ({
      toggleTheme,
      isThemeDark,
    }),
    [toggleTheme, isThemeDark]
  );

  return (
    <PreferencesContext.Provider value={preferences}>
      <NavigationContainer theme={theme}>
        <RootStackScreen />
        <FlashMessage position="center" floating />
      </NavigationContainer>
    </PreferencesContext.Provider>
  );
};

I already renamed one Home to HomeSide and another one to HomeBottom but that didn't help. I suspect it could be because of this: HomeStack.Screen name={route.name} ?

I tried to make a map of my navigators and I think it looks like that:

RootStack.Navigator
    RootStack.Screen name="Loading"
    RootStack.Screen name="AppSideDrawerScreen"
        AppSideDrawer.Navigator
            AppSideDrawer.Screen name="HomeSide"
                AppBottomTabs.Navigator
                    AppBottomTabs.Screen name="HomeBottom"
                        HomeStack.Navigator
                            HomeStack.Screen name={route.name}
                    AppBottomTabs.Screen name="History"
                    AppBottomTabs.Screen name="Groups"
            AppSideDrawer.Screen name="History"
                HistoryStack.Navigator
                    HistoryStack.Screen name={route.name}
            AppSideDrawer.Screen name="Groups"
                GroupsStack.Navigator
                    GroupsStack.Screen name={route.name}
            AppSideDrawer.Screen name="Settings"
                SettingsStack.Navigator
                    SettingsStack.Screen name={route.name}
    RootStack.Screen name="AddGroup"
    RootStack.Screen name="GroupList"

Solution

  • I ended up calling the screens with different names, and then added a title to use when displaying the screen name to the user.

    I also added a switch inside OpenDrawerIcon as the logic was a bit more complex there:

    const OpenDrawerIcon = (route, navigation) => {
      let titleName = "";
      switch (route.name) {
        case "HomeStack":
          titleName = "Home";
          break;
        case "HistoryStack":
          titleName = "History";
          break;
        case "GroupsStack":
          titleName = "Groups";
          break;
        case "SettingsStack":
          titleName = "Settings";
          break;
        default:
          titleName = "DefaultTitle";
      }
    
      return {
        headerLeft: () => (
          <TouchableOpacity onPress={() => navigation.openDrawer()}>
            <Entypo
              name="menu"
              size={32}
              style={{ padding: 5 }}
            />
          </TouchableOpacity>
        ),
        title: titleName,
      };
    };
    

    And here are my screens:

    const HomeStackScreen = () => {
      return (
        <HomeStack.Navigator screenOptions={screenOptionWithHeaderStyle}>
          <HomeStack.Screen
            name="HomeStack"
            component={Home}
            options={({ route, navigation }) => OpenDrawerIcon(route, navigation)}
            // options={OpenDrawerIcon} // Old way
          />
        </HomeStack.Navigator>
      );
    };