12.2 Nested Navigation

Building on what you have learned from the last module, it is time to look at how to manage the multi-level navigation requirements common to modern mobile applications. The initial setup is the same as for the Stack Navigator. Make sure that these dependencies are installed in your project.

yarn add @react-navigation/native
1
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context
1

# Tab Navigation

The usage instructions for Tab.Navigator are very similar to the Stack.Navigator. Before you can use the tab navigator, you need to install the NPM module.

yarn add @react-navigation/bottom-tabs
1

Then import the createBottomNavigator function in your App.js module.



 

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
1
2
3

Next create a new instance of the Tab object using the imported function. When invoked, it returns on object with two properties containing React components: Screen and Navigator.

const Tab = createBottomTabNavigator();
1

Then in the JSX, replace Stack.Navigator with Tab.Navigator and replace Stack.Screen with Tab.Screen.

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="About" component={AboutScreen} />
        <Tab.Screen name="Contact" component={ContactScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
1
2
3
4
5
6
7
8
9
10
11

# Add icons

It is easy to add icons to your tabs using the included react-native-vector-icons packages that Expo installs by default.

import Ionicons from 'react-native-vector-icons/Ionicons';
1

Then using the options prop on the Tab.Screen component, set the tabBarIcon. To make the icon dynamic, use a function that is given the focused state, color, and size params.




 
 
 
 
 


<Tab.Screen
  name="Contact"
  component={ContactScreen}
  options={{
    tabBarIcon: ({ focused, size, color }) => (
      <Ionicons name={'ios-paper-plane'} size={size} color={color} />
    ),
  }}
/>
1
2
3
4
5
6
7
8
9

You have complete flexibility in styling your tabs to match your designs. For example, let's use indigo as our key brand colour and then set up the tab bar options.

<Tab.Navigator
  tabBarOptions={{
    activeTintColor: 'hsl(275, 100%, 23%)', // indigo
    inactiveTintColor: 'hsl(275, 15%, 60%)',
    style: {backgroundColor: 'hsl(275, 100%, 93%)'}
  }}
>
1
2
3
4
5
6
7

Read the docs ...

For a full list of options, see the React Navigation API docs (opens new window).

# Nested Navigators

It is pretty common to have a tab navigator as the primary means of navigation in a mobile app. And it is also quite common to have a nested stack navigator in a tab screen to allow the user to view more detailed content or perhaps to edit content.

So, how do you do that?

Because the React Navigation library uses factory functions to create unique instances of the various navigator objects, e.g. Stack or Tab, your application can deploy them in combination. Each will maintain its own independent navigation state.

Lets add a stack navigator to the home screen inside our tab navigator.

# Create a HomeDetailsScreen component

This will be added to the home screen stack navigator in a couple of steps from now.

function HomeDetailsScreen() {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Home Details!</Text>
      </View>
    </SafeAreaView>
  );
}
1
2
3
4
5
6
7
8
9

# Install the library

Next you will need to add the stack navigator library to your project.

yarn add @react-navigation/stack
1

And then create a new Stack instance ...

import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();
1
2
3

# Create a new HomeStackScreen component

When the "Home" tab is selected in the Tab.Navigator you need to give it the Stack.Navigator to render, which will in turn render either the HomeScreen or the HomeDetailsScreen.

function HomeStackScreen() {
  return (
    <HomeStack.Navigator>
      <HomeStack.Screen name="Home" component={HomeScreen} />
      <HomeStack.Screen name="HomeDetails" component={HomeDetailsScreen} />
    </HomeStack.Navigator>
  );
}
1
2
3
4
5
6
7
8

Stack state is maintained

Notice that when you change tabs and then go back to a tab with a nested Stack.Navigator, the previous state of that stack is unchanged. i.e. it is still showing the same screen as before you switched tabs.

# Passing Props to Screens

It is worth noting that if you want to pass additional props to your Screen elements inside the Stack or Tab Navigators then you need to change how you write the element.

Here is an example using the Tab Navigator. The <Tab.Navigator> uses the function for screenOptions to set the icon on each tab.

The two <Tab.Screen> components are written different. The Home tab simply uses the component={HomeScreen} prop to load that component. However, the List tab needs to pass an array of data to the ListScreen component. We accomplish this by adding a function between the opening and closing tags. The navigation props get passed to this function and we can write those to ListScreen with {...props}. Then we can add our additional props. In this case we create a prop called people and add our state variable as the value for that prop.

<NavigationContainer>
  <Tab.Navigator
    screenOptions={({ route }) => ({
      tabBarIcon: ({ focused, color, size }) => {
        let iconName;
        if (route.name === 'Home') {
          iconName = focused ? 'home' : 'home-outline';
        } else {
          iconName = focused ? 'list-circle' : 'list-circle-outline';
        }
        return <Ionicons name={iconName} size={size} color={color} />;
      },
    })}
  >
    <Tab.Screen name="Home" component={HomeScreen} />

    <Tab.Screen name="List">
      {(props) => <ListScreen {...props} people={people} />}
    </Tab.Screen>
  </Tab.Navigator>
</NavigationContainer>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Focus Aware StatusBar

When your app design calls for different background colours for the header on difference screens, the normal behaviour of the StatusBar component isn't helpful. It will keep the settings (light or dark) of the last component to render.

The solution is to create your own FocusAwareStatusBar, using the useIsFocused hook from React Navigation. This will cause your status bar to re-render when the screen is in focus, thereby updating the StatusBar with the correct settings.

import React from 'react';
import { StatusBar } from 'expo-status-bar';
import { useIsFocused } from '@react-navigation/native';

function FocusAwareStatusBar(props) {
  const isFocused = useIsFocused();
  return isFocused ? <StatusBar {...props} /> : null;
}

export default FocusAwareStatusBar;
1
2
3
4
5
6
7
8
9
10

You may now use this component as needed in your screen component JSX, with the same props as the regular Expo StatusBar.

# Drawer Navigation

Install the drawer module.

yarn add @react-navigation/drawer
1

Import and run the factory function.

import { createDrawerNavigator } from '@react-navigation/drawer';

const Drawer = createDrawerNavigator();
1
2
3

Extract the Tab navigation to its own TabScreen component and then update the top level navigation in App.js to look like this ...

export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator initialRouteName="Home">
        <Drawer.Screen name="Home" component={TabScreen} />
        <Drawer.Screen name="Setting" component={SettingsScreen} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}
1
2
3
4
5
6
7
8
9
10

# Resources

# GitHub repo

rlmckenney/mad9135-nested-nav-demo (opens new window)

# API docs

Last Updated: : 12/1/2021, 12:24:23 PM