Updated October 15, 2019
Complex Navigation Example with React Navigation v4
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 use v4 of React Navigation and React Native v0.60. For v5 of React Navigation please read the updated version.
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) {
// Grab all the routes the parent defines and add it the list
availableRoutes = [
...availableRoutes,
...Object.keys(parent.router.childRouters),
];
}
// Recursively work up the tree until there are none left
availableRoutes = [...availableRoutes, ...getAvailableRoutes(parent)];
}
// De-dupe the list and then remove the current route from the list
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. 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.