11.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
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
# 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
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'
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()
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>
);
}
# 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'
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} />
)
}}
/>
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%)'}
}}
>
Read the docs ...
For a full list of options, see the React Navigation API docs.
# 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>
)
}
# Install the library
Next you will need to add the stack navigator library to your project.
yarn add @react-navigation/stack
And then create a new Stack
instance ...
import { createStackNavigator } from '@react-navigation/stack'
const Stack = createStackNavigator()
# 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>
)
}
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.
# 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
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
Import and run the factory function.
import { createDrawerNavigator } from '@react-navigation/drawer'
const Drawer = createDrawerNavigator()
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>
)
}
# Resources
# GitHub repo
rlmckenney/mad9135-nested-nav-demo