Search code examples
react-nativenavigationreact-navigationreact-navigation-stack

How do I define multiple types of navigation containers in the same component?


I am new to the world of react native, so I need a hand with structuring my navigation pages. I want my app to have instagram like structure - that is the main page should be createBottomTabNavigator From there I have few different pages, and I can switch between them easily. Here is my code:

export default const MyTabs = ({ currentUser, navigation }) => {
  return (
    <Tab.Navigator initialRouteName="Home">
      <Tab.Screen
        name="Home"
        component={HomeScreen}
      />
      <Tab.Screen
        name="Network"
        component={NetworkScreen}
      />
      <Tab.Screen
        name="Profile"
        component={ProfileScreen}
        options={{
          tabBarIcon: ({ color, size }) => (
            <Entypo name="map" color={color} size={26} />
          ),
        }}
      />
    </Tab.Navigator>
  );
};

Then in my App.js file I call it like this:

return (
      <Provider store={store}>
        <NavigationContainer>
          <MyTabs />
        </NavigationContainer>
      </Provider>
    );

The above code works fine. The problem is when I want to perform more complicated navigation. For instance, in my home page I have list of objects and once, I click on these objects I want to navigate to a new page - with Stack navigator. How do I do so, and where should I define this page? Do I define it somehow inside my Home page, or inside my App.js page? In my mind it should be something like this:

<Stack.Screen
              name="Details"
              component={DetailScreen}
            />

I tried to put it inside App.js, something like this:

<Provider store={store}>
  <NavigationContainer>
    <MyTabs />
    <Stack.Navigator>
      <Stack.Screen
        name="Meetup Details"
        component={MeetupDetails}
        options={{
          headerTitle: "Meetup Details",
        }}
      />
    </Stack.Navigator>
  </NavigationContainer>
</Provider>;

But ofc it didn't work.

Also I have another question - in my bottom tab I can navigate to My profile screen and that works fine. But I want to view other profiles as well. So my bottom tab is reserved for my own profile, but how do I define another Profile screen, which can go to other user's profile? Does it need to be with stack navigator? Does it again need to be defined in App.js? Is it ok to define two different navigators (bottom tab and stack) with the same page (Profile)?


Solution

  • Usually, when you have a bottom tab navigator, each tab will be a StackNavigator

    export default const MyTabs = ({ currentUser, navigation }) => {
      return (
        <Tab.Navigator initialRouteName="Home">
          <Tab.Screen
            name="Home" // here this would be BottomTabRoutes.HOME
            component={HomeNavigator}
          />
          <Tab.Screen
            name="Network"  // here this would be BottomTabRoutes.NETWORK
            component={NetworkNavigator}
          />
          <Tab.Screen
            name="Profile"  // here this would be BottomTabRoutes.PROFILE
            component={ProfileNavigator}
            options={{
              tabBarIcon: ({ color, size }) => (
                <Entypo name="map" color={color} size={26} />
              ),
            }}
          />
        </Tab.Navigator>
      );
    };

    And then your MeetupDetails screen will be contained inside the HomeNavigator which is a StackNavigator

    export const HomeNavigator = () => {
      return (
        <Stack.Navigator
          screenOptions={{
           ...
          }}>
          <Stack.Screen
            name={HomeRoutes.HOME}
            component={Home}
            options={{
              headerStyle: styles.header,
              title: 'Home'
            }}
          />
           <Stack.Screen
            name={HomeRoutes.MEETUP_DETAILS}
            component={MeetupDetails}
            options={{
              headerStyle: styles.header,
              title: 'Meetup Details'
            }}
          />
        </Stack.Navigator>
        
      }

    Also, I would suggest to store your routes names in an enum, in this way it will be easier and their possible route props in a tpye Something like this:

    export enum BottomTabRoutes {
      HOME = 'Home',
      NETWORK = 'Network',
      PROFILE = 'Profile',
    }
    
    export type BottomTabRouteProps = {
      [BottomTabRoutes.HOME]: {title: string}; // this is another parameter example
      [BottomTabRoutes.PROFILE]: undefined;
      [BottomTabRoutes.NETWORK]: undefined;
    };

    And the same for each Stack Navigator:

    export enum HomeRoutes = {
      HOME = 'Home',
      MEETUP_DETAILS = 'Meetup Details',
    }
    
    
    export type HomeRouteProps = {
      [HomeRoutes.HOME]: undefined
      [HomeRoutes.MEETUP_DETAILS: { someParamName: SomeType } // this might be a parameter you'll send through navigation
    }
    
    // Therefore, your MeetupDetails screen will look something like this: 
    
    export const MeetupDetails = (props: StackScreenProps<HomeRouteProps, HomeRouteProps.MEETUP_DETAILS>,) => {
      
      const navigationParam = props.route.params.someParamName
    
      
      return (
        <View>
        ...
        
        </View>
        
        )
        
      }

    EDIT

    Regarding the Profile Screen, I believe you can have something like this:

    export const MainNavigator = () => {
      return (
        <Stack.Navigator screenOptions={{headerShown: false}}>
          <Stack.Screen name={MainNavigatorRoutes.HOME} component={HomeNavigator} />
          <Stack.Screen name={MainNavigatorRoutes.USER} component={UserNavigator} />
        </Stack.Navigator>
      );
    };
    
    
    export const UserNavigator = () => {
      return (
        <Stack.Navigator
          screenOptions={({navigation}) => ({
           ...)}>
          <Stack.Screen
            name={UserNavigatorRoutes.SOME_OTHER_SCREEN}
            component={UserSettings}
          />
          <Stack.Screen
            name={UserNavigatorRoutes.USER_PROFILE}
            component={UserProfile}
          />
        </Stack.Navigator>
      );
    };
    
    
    export const HomeNavigator = () => {
      return (
        <Stack.Navigator screenOptions={{title: ''}} >
         <Stack.Screen
              name={HomeNavigatorRoutes.HOME_TABS}
              component={HomeTabNavigator}
              options={{
                headerShown: false,
              }}
            />
        </Stack.Navigator>
      );
    };
    
    export const HomeTabNavigator = () => (
      <Stack.Navigator >
        <Stack.Screen
          name={HomeTabNavigatorRoutes.TAB_NAVIGATOR}
          component={MyTabs}
          options={{headerShown: false}}
        />
      </Stack.Navigator>

    Basically, the structure will look like this: MainNavigator = { HomeNavigator, UserNavigator }. In this way, each navigator will be separate and you should be able to navigate to any screen from UserNavigator even if you are in a screen from UserNavigator