Last Updated: October 15, 2019

Hey - React Navigation has a new API! To read an updated article please check out the new tutorial.

I've found people can be intimidated by combining different navigators in React Navigation to accomplish more "complex" navigation patterns. Today I want to briefly walk you through a more complex navigation set up. It will include:

Switch Navigator used to represent our authenticated vs. un-authenticated app status

Stack Navigator for normal right-to-left navigation in numerous places (authentication screens, stacks for each tab)

Stack Navigator for bottom-to-top navigation

Tab Navigator

Drawer Navigator

Be sure to install React Navigation before you try using the library. These instructions are up to date as of v4 of React Navigation and React Native v0.60.

Prereq

Before we get started I'll be adding an Example.js file to serve as a screen for all of our routes (it is just a demo after all). This component generates a random background color and will display all of the available routes from the current screen.

Example.js

import React from 'react' ; import { View , TouchableOpacity , Text } from 'react-native' ; const getAvailableRoutes = ( navigation ) => { let availableRoutes = [ ] ; if ( ! navigation ) return availableRoutes ; const parent = navigation . dangerouslyGetParent ( ) ; if ( parent ) { if ( parent . router && parent . router . childRouters ) { availableRoutes = [ ... availableRoutes , ... Object . keys ( parent . router . childRouters ) , ] ; } availableRoutes = [ ... availableRoutes , ... getAvailableRoutes ( parent ) ] ; } return [ ... new Set ( availableRoutes ) ] . filter ( ( route ) => route !== navigation . state . routeName ) ; } ; const getRandomColor = ( ) => { var letters = '0123456789ABCDEF' ; var color = '#' ; for ( var i = 0 ; i < 6 ; i ++ ) { color += letters [ Math . floor ( Math . random ( ) * 16 ) ] ; } return color ; } ; const Example = ( { navigation } ) => { return ( < View style = { { flex : 1 , alignItems : 'center' , justifyContent : 'center' , backgroundColor : getRandomColor ( ) , } } > { getAvailableRoutes ( navigation ) . map ( ( route ) => ( < TouchableOpacity onPress = { ( ) => navigation . navigate ( route ) } key = { route } style = { { backgroundColor : '#fff' , padding : 10 , margin : 10 , } } > < Text > { route } </ Text > </ TouchableOpacity > ) ) } </ View > ) ; } ; export default Example ;

With that done lets get started.

Switch Navigator

To accomplish the switching between different "states" of a user's journey we'll use a switch navigator so the user can't go back. Obviously, we'll have a screen for the main app journey. We'll also have one for un-authenticated users.

Additionally, I like to add a Loading screen of sorts. Typically this won't display anything - it's just there to determine if a user is authenticated or not and route them to the right place.

index.js

import React from 'react' ; import { createAppContainer , createSwitchNavigator } from 'react-navigation' ; import { createStackNavigator } from 'react-navigation-stack' ; import { createBottomTabNavigator } from 'react-navigation-tabs' ; import { createDrawerNavigator } from 'react-navigation-drawer' ; import Example from './screens/Example' ; const App = createSwitchNavigator ( { Loading : { screen : Example , } , Auth : { screen : Example , } , App : { screen : Example , } , } ) ; export default createAppContainer ( App ) ;

Want to become the best possible React Native developer? Consider getting a React Native School membership! You'll get access to hundreds of tutorials, a dozen classes, and a community full of React Native developers.

Auth Stack Navigator

If a user is not authenticated we'll set up a Stack navigator for them to go from a landing screen, sign in, create account, forgot password, or reset password. The typical options you see when you need to authenticate.

index.js

const AuthStack = createStackNavigator ( { Landing : { screen : Example , navigationOptions : { headerTitle : 'Landing' , } , } , SignIn : { screen : Example , navigationOptions : { headerTitle : 'Sign In' , } , } , CreateAccount : { screen : Example , navigationOptions : { headerTitle : 'Create Account' , } , } , ForgotPassword : { screen : Example , navigationOptions : { headerTitle : 'Forgot Password' , } , } , ResetPassword : { screen : Example , navigationOptions : { headerTitle : 'Reset Password' , } , } , } ) ; const App = createSwitchNavigator ( { Loading : { screen : Example , } , Auth : { screen : AuthStack , } , App : { screen : Example , } , } ) ; export default createAppContainer ( App ) ;

App Tabs

Once the user is in the app we'll use tabs to give them the ability to access the main features of our app - a feed, search, and a discover page. We'll then replace the App item in our App navigator with the result of creating our tabs.

The output of creating any navigator is just a component so we can nest them infinitely in React Navigation.

index.js

const MainTabs = createBottomTabNavigator ( { Feed : { screen : Example , navigationOptions : { tabBarLabel : 'Feed' , } , } , Search : { screen : Example , navigationOptions : { tabBarLabel : 'Search' , } , } , Discover : { screen : Example , navigationOptions : { tabBarLabel : 'Discover' , } , } , } ) ; const App = createSwitchNavigator ( { Loading : { screen : Example , } , Auth : { screen : AuthStack , } , App : { screen : MainTabs , } , } ) ;

Stack Navigator for Each App Tab

Just like we nested the MainTabs in our App navigator we'll allow each tab in our app to have its own stack navigator. Doing it this way means that each tab is going to carry its own state so a user can go to the details screen of one tab, switch to another, and when coming back are able to maintain the same state for each tab.

Additionally, with this example you can see that navigators will grab the closest matching route name. That means we can reuse screen names and each stack will just grab the closest available Details screen either in its stack or above it in the navigator hierarchy.

index.js

const FeedStack = createStackNavigator ( { Feed : { screen : Example , navigationOptions : { headerTitle : 'Feed' , } , } , Details : { screen : Example , navigationOptions : { headerTitle : 'Details' , } , } , } ) ; const SearchStack = createStackNavigator ( { Search : { screen : Example , navigationOptions : { headerTitle : 'Search' , } , } , Details : { screen : Example , navigationOptions : { headerTitle : 'Details' , } , } , } ) ; const DiscoverStack = createStackNavigator ( { Discover : { screen : Example , navigationOptions : { headerTitle : 'Discover' , } , } , Details : { screen : Example , navigationOptions : { headerTitle : 'Details' , } , } , } ) ; const MainTabs = createBottomTabNavigator ( { Feed : { screen : FeedStack , navigationOptions : { tabBarLabel : 'Feed' , } , } , Search : { screen : SearchStack , navigationOptions : { tabBarLabel : 'Search' , } , } , Discover : { screen : DiscoverStack , navigationOptions : { tabBarLabel : 'Discover' , } , } , } ) ;

App Drawer

Same story with a drawer. We'll create the navigator (we're also creating a settings stack to give us a reason for the drawer) and render that as a screen.

This time we'll replace rendering MainTabs with MainDrawer and render our tabs within the drawer. Building this hierarchy means we're just adding more navigators but everything that was already there will continue to work.

index.js

const SettingsStack = createStackNavigator ( { SettingsList : { screen : Example , navigationOptions : { headerTitle : 'Settings List' , } , } , Profile : { screen : Example , navigationOptions : { headerTitle : 'Profile' , } , } , } ) ; const MainDrawer = createDrawerNavigator ( { MainTabs : MainTabs , Settings : SettingsStack , } ) ; const App = createSwitchNavigator ( { Loading : { screen : Example , } , Auth : { screen : AuthStack , } , App : { screen : MainDrawer , } , } ) ;

Modal Style Stack Navigator

Finally, we want to add a navigator that moves from bottom to top and will cover any other screen. That means it needs to be at the root most position of our stack. If it's at the root then it will be available to be rendered from any of its children.

index.js

const AppModalStack = createStackNavigator ( { App : MainDrawer , Promotion1 : { screen : Example , } , } , { mode : 'modal' , headerMode : 'none' , } ) ; const App = createSwitchNavigator ( { Loading : { screen : Example , } , Auth : { screen : AuthStack , } , App : { screen : AppModalStack , } , } ) ; export default createAppContainer ( App ) ;

Final Navigator Code

Our final code.

index.js

import React from 'react' ; import { createAppContainer , createBottomTabNavigator , createDrawerNavigator , createStackNavigator , createSwitchNavigator , } from 'react-navigation' ; import Example from './screens/Example' ; const AuthStack = createStackNavigator ( { Landing : { screen : Example , navigationOptions : { headerTitle : 'Landing' , } , } , SignIn : { screen : Example , navigationOptions : { headerTitle : 'Sign In' , } , } , CreateAccount : { screen : Example , navigationOptions : { headerTitle : 'Create Account' , } , } , ForgotPassword : { screen : Example , navigationOptions : { headerTitle : 'Forgot Password' , } , } , ResetPassword : { screen : Example , navigationOptions : { headerTitle : 'Reset Password' , } , } , } ) ; const FeedStack = createStackNavigator ( { Feed : { screen : Example , navigationOptions : { headerTitle : 'Feed' , } , } , Details : { screen : Example , navigationOptions : { headerTitle : 'Details' , } , } , } ) ; const SearchStack = createStackNavigator ( { Search : { screen : Example , navigationOptions : { headerTitle : 'Search' , } , } , Details : { screen : Example , navigationOptions : { headerTitle : 'Details' , } , } , } ) ; const DiscoverStack = createStackNavigator ( { Discover : { screen : Example , navigationOptions : { headerTitle : 'Discover' , } , } , Details : { screen : Example , navigationOptions : { headerTitle : 'Details' , } , } , } ) ; const MainTabs = createBottomTabNavigator ( { Feed : { screen : FeedStack , navigationOptions : { tabBarLabel : 'Feed' , } , } , Search : { screen : SearchStack , navigationOptions : { tabBarLabel : 'Search' , } , } , Discover : { screen : DiscoverStack , navigationOptions : { tabBarLabel : 'Discover' , } , } , } ) ; const SettingsStack = createStackNavigator ( { SettingsList : { screen : Example , navigationOptions : { headerTitle : 'Settings List' , } , } , Profile : { screen : Example , navigationOptions : { headerTitle : 'Profile' , } , } , } ) ; const MainDrawer = createDrawerNavigator ( { MainTabs : MainTabs , Settings : SettingsStack , } ) ; const AppModalStack = createStackNavigator ( { App : MainDrawer , Promotion1 : { screen : Example , } , } , { mode : 'modal' , headerMode : 'none' , } ) ; const App = createSwitchNavigator ( { Loading : { screen : Example , } , Auth : { screen : AuthStack , } , App : { screen : AppModalStack , } , } ) ; export default createAppContainer ( App ) ;

You can find a working example on Snack.