Nesting Tab and Stack Navigators in React Native and Expo Apps

Navigation is an essential part of any mobile app that has multiple screens leading to a variety of interactions for the user. Building a working navigation system often requires us to create a structure that allows for different kinds of navigators working together.

Using react-navigation, you can definitely nest different types of navigators in a React Native app. The term nesting, when it comes to navigators, refers to rendering one navigator inside a screen of another navigator.

The need to nest navigators arises when you want a user to respond to different sections of the app. For example, an app that requires a user to enter credentials to access different features of the app—they have to go through either a login or signup screen. The home or the main screen might contain different tabs (think of Instagram), but the login and sign up screens are two separate screens that are pushed and pop one after the other.

A few of the possible scenarios for using nesting navigators include:

  • Stack navigator nested inside drawer navigator
  • Tab navigator nested inside stack navigator
  • Stack navigator nested inside a tab navigator

In this tutorial, let’s examine one of the above scenarios by nesting a tab navigator inside a stack navigator. Whether you’re following from the previous tutorial on building a stack navigator using a component-based configuration with the latest version of the react-navigation library (link below), or not, here’s the source code of the Expo demo app that’s going to be leveraged. This demo app already has a stack navigator running—you can download the source code from the GitHub repo here.

You can read the complete post on setting up a Stack Navigator using react-navigation version 5 here:

Table of contents

  • Install dependencies
  • Create a mock screen
  • Create a tab navigator
  • Adding icon and changing active tint color
  • Passing screenOptions in a tab navigator
  • Updating the header title for the nested child navigator
  • Conclusion

Requirements

Requirements for this tutorial are simple. Have the following installed on your local dev environment:

  • Node.js version >= 10.x.x installed
  • Have access to one package manager such as npm or yarn
  • Latest expo-cli version installed or use npx

Do note that, without digging much into the configuration of native binaries with the react-navigation library, I’m going to use a project that’s already generated using expo-cli. If you wish to start fresh, choose the blank template.

Install dependencies

Install the following dependency to set up a tab navigator. Run the following command from a terminal window:

yarn add @react-navigation/bottom-tabs

This package will allow the app to have a simple tab bar appear at the bottom of the screen and switch between different routes. The demo app we’re going to build will consist of two tabs. We’re going to nest the tab navigator inside the stack navigator and create a mock screen for the second tab.

Create a mock screen

Even though the current app structure has three different screen components (open src/screens to view them), let’s create another screen component called Profile that will act as the second tab. Create a new file called src/screens/Profile.js with the following code snippet:

import React from 'react'
import { StyleSheet, View, Text } from 'react-native'

function Profile(props) {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Profile Tab</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#ebebeb'
  },
  text: {
    color: '#101010',
    fontSize: 24,
    fontWeight: 'bold'
  }
})

export default Profile

Create a tab navigator

In this section, let’s set up a basic tab navigator. Start by renaming the file MainStackNavigator to AppNavigator.js in the directory src/navigation.

After renaming the routes config file, and after other import statements, import the createBottomTabNavigator from @react-navigation/bottom-tabs, as well as the Profile screen component.

import * as React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
// add this after other import statements
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'

import Home from '../screens/Home'
import Detail from '../screens/Detail'
import Settings from '../screens/Settings'
// add this after other import statements
import Profile from '../screens/Profile'

Then create an instance of the createBottomTabNavigator called Tab as shown below:

// after other instances
const Tab = createBottomTabNavigator()

Next, create a function called MainTabNavigator(). Using Tab.Navigator, you can define the structure of the routes, and using Tab.Screen, you can define each of the routes.

Let’s define the following tab routes for now: Home and Profile.

function MainTabNavigator() {
  return (
    <Tab.Navigator>
      <Tab.Screen name='Home' component={Home} />
      <Tab.Screen name='Profile' component={Profile} />
    </Tab.Navigator>
  )
}

Now, in the MainStackNavigator(), instead of passing the Home screen, let’s pass the MainTabNavigator.

<Stack.Screen name='Home' component={MainTabNavigator} />

Lastly, to make all of this work, open the App.js file in the root of the project and modify the statement that imports the MainStackNavigator with the correct file name:

import React from 'react'

// make sure this matches the file name of navigator config
import MainStackNavigator from './src/navigation/AppNavigator'

export default function App() {
  return <MainStackNavigator />
}

Go back to the terminal window, execute expo start, and open up an Expo client inside a simulator or a real device. You’re going to get the following result:

Adding an icon and changing active tint color

From the last image, you’ll notice that the active tab is highlighted by a blue tint color, and the non-active tab is gray. Let’s change this tint color.

Open the AppNavigator.js file, and in the Tab.Navigator add a prop called tabBarOptions. This prop allows you to customize the tab bar shared between different routes.

Add the following:

<Tab.Navigator
  tabBarOptions={{
    activeTintColor: '#101010'
  }}>
  {/* rest remains same */}
</Tab.Navigator>

Go to the simulator device—you’re going to notice that the active tab bar label is black, a change from the previous blue.

Let’s add some icons to the tab bar. Start by importing the Ionicons from @expo/vector-icons.

import { Ionicons } from '@expo/vector-icons'

Then, in each Tab.Screen, add an options prop that has a property of tabBarIcon. This function returns the component Ionicons. Pass the arguments color and size to maintain the active tint color.

<Tab.Navigator
  tabBarOptions={{
    activeTintColor: '#101010'
  }}>
  <Tab.Screen
    name='Home'
    component={Home}
    options={{
      tabBarIcon: ({ color, size }) => (
        <Ionicons name='ios-home' color={color} size={size} />
      )
    }}
  />
  <Tab.Screen
    name='Profile'
    component={Profile}
    options={{
      tabBarIcon: ({ color, size }) => (
        <Ionicons name='ios-person' size={size} color={color} />
      )
    }}
  />
</Tab.Navigator>

Here is the output:

You can even change the background of the tab bar by adding a style property to tabBarOptions.

<Tab.Navigator
  tabBarOptions={{
    activeTintColor: '#101010',
    style: {
      backgroundColor: '#ffd700'
    }
  }}>
  {/* rest remains same */}
</Tab.Navigator>

Here’s the output for the above snippet:

Passing screenOptions in a Tab Navigator

The previous section is one way to add icons to each route or screen in the tab bar. There’s another way you can do it by passing screenOptions in the wrapper Tab.Navigator. This prop is used to modify or add common styles to a navigator.

function MainTabNavigator() {
  return (
    <Tab.Navigator
      tabBarOptions={{
        activeTintColor: '#101010',
        style: {
          backgroundColor: '#ffd700'
        }
      }}
      screenOptions={({ route }) => ({
        tabBarIcon: ({ color, size }) => {
          let iconName
          if (route.name == 'Home') {
            iconName = 'ios-home'
          } else if (route.name == 'Profile') {
            iconName = 'ios-person'
          }
          return <Ionicons name={iconName} color={color} size={size} />
        }
      })}>
      <Tab.Screen name='Home' component={Home} />
      <Tab.Screen name='Profile' component={Profile} />
    </Tab.Navigator>
  )
}

There’s no change in the actual function of the tab navigator from the previous section, as you can notice below:

Updating the header title for the nested child navigator

Right now, the title for each tab screen is going to be the same. This is because the root navigator (which here is the stack navigator) structure is going to look at its immediate children, which are the Home, Detail, and Settings screens. In the current scenario, if you’re to set the title for the Profile screen passing the prop options, it isn’t going to work.

This is because the Profile screen is a child of the tab navigator and not the stack navigator. The tab navigator is nested inside the stack navigator, and thus, profile isn’t the immediate child to the stack navigator.

For each tab to have its own title (since the tab navigator is nested inside the stack navigator), you have to determine the title for a specific tab screen based on the navigation state from the property route.state.

This can be done by defining a helper function called getHeaderTitle that has route as its parameter. Why pass route? Because it contains the state property, which refers to the child’s navigator state, and the value of the currently active route name can be obtained from this state.

Add a function called getHeaderTitle in the AppNavigator.js file:

function getHeaderTitle(route) {
  const routeName = route.state
    ? route.state.routes[route.state.index].name
    : route.params?.screen || 'Home'

  switch (routeName) {
    case 'Home':
      return 'Home'
    case 'Profile':
      return 'Profile'
  }
}

Then, as per the recommended method, add the options prop to the Stack.Screen route, whose value is Home.

<Stack.Screen
  name='Home'
  component={MainTabNavigator}
  options={({ route }) => ({
    headerTitle: getHeaderTitle(route)
  })}
/>

Now, when visiting the Profile tab, you’re going to get the desired title in the header.

Conclusion

Congratulations! You’ve completed this tutorial.

In this tutorial, we discuss only one scenario for nesting navigators. The main objective here was to become familiar with the component-based configuration of the tab navigator in the latest version of the react-navigation library.

The link to the complete Tab Navigator API is here, and I’d recommend you to check it out.

You can find the complete code for this tutorial at this GitHub repo.

If you’d like to receive more React Native tutorials in your inbox, you can sign up for my newsletter here.

Fritz

Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 0 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

wix banner square