React Navigation: Stacks, Tabs, and Drawers … Oh my!

This is Part III of a series on React Navigation. If you’ve never used this library before, check out my other posts: Up and Running and Styling Navigators, Custom Transition Animations, and Redux Integration.

When you navigate around within your app, does it naturally flow from screen to screen, or is a tangled maze of dead ends?

Navigation is all about organizing your content. If you want to be able to switch around between a few different unrelated sections of your app, tabs might be a good option. If your app needs to follow a specific sequence of events (i.e. a user must log in and THEN they can send a message), you’re probably looking at a stack. The React Navigation library makes it possible to wire up complex routing and layouts. In this post, we’ll explore the different types of navigation available to us and make a simple app that combines all of them.

A ‘Navigator’ is the React component that a user interacts with in order to move from screen to screen within your app. Navigators can also keep track of their own history, so when a user presses the back button, they move backwards through the screens they were previously viewing. Back behavior seems like a no-brainer but trust me, history can get complicated quickly.

It’s worth repeating: A navigator is just another React component. They have some extra built-in functionality but at the end of the day they are rendered to the screen just like everything else.

Navigators come in three flavors:

StackNavigator: When user taps a link, a new screen is put on top of old screens.
TabNavigator: User navigates to different screens by tapping on tabs along the top or bottom of the screen.
DrawerNavigator: A slide-out ‘drawer’ contains links to different screens.
Stack, Tab, and Drawer Navigators

Navigators have visible elements (i.e. the tabs/drawer/header), as well as functionality — they handle how we get from one screen to the next, and manage animated transitions between screens, if there are any.

Here’s the powerful bit:

The ‘screen’ a navigator renders may be an entire Navigator in itself.

This allows us to nest ‘navigators within navigators’ and create navigation structures as complex as we like. Don’t worry if this makes your head spin, we’ll explore the concept in detail throughout this post.


We’re going to be creating a simple app that includes all three navigator types. We want to be able to pull out the drawer from anywhere in the app, so our base component will be a DrawerNavigator. Our drawer will contain three menu options:

  • Stack: A StackNavigator with an ‘ItemList’ screen containing a list of items that may be tapped to view their individual ‘Item’ screen.
  • Tabs: A TabNavigator with three tabs — ‘TabA’, ‘TabB’, and ‘TabC’
  • Plain: A plain old component appropriately named ‘Plain’ — it’s just here to demonstrate the simplest possible option.
Navigation structure of our app

Ready to dive into the code?

First, we’ll create a bunch of screens. I’m going to skip over the code for the screens since they’re mostly just <View /> elements with a few styles applied (check the repo if you’re interested). Then, we’ll link them together using our StackNavigator and our TabNavigator. Finally, we’ll include these Navigators as screens in our Drawer.

I’m going to create a file called navigators.js that I’ll use to organize all of my navigation logic. To kick things off, I’ll import the three Navigator constructors from react-navigation, along with all my screens:


StackNavigator

Let’s wire up the StackNavigator. I went into more detail about how to do this in my first post in this series, but the short version is that you’ll import { StackNavigator } from 'react-navigation' and then pass it a few config objects containing initialization options. After we do that, all of the screens that the navigator renders will get a navigation prop that we can use to go from screen to screen.

We’ll assign our StackNavigator to a variable named Stack. StackNavigator takes two config objects as arguments when you initialize it: StackNavigator(routeConfig, navigatorConfig) and it’ll look something like this:

The first argument (the routeConfig object) is an object keyed by whatever name you want to give to each route. The value associated with each key is an object with a screen property that tells your StackNavigator which screen to render for that route name.

The second argument (the navigatorConfig object) allows you to set initial values on the navigator. We’ll use it to tell our StackNavigator which screen to initially render by setting its initialRouteName property to the string 'ItemList' which we have set to our ‘ItemList’ component in the routeConfig object above. Check out the other properties you can set.

Now that our Navigator is configured, let’s take a look at how navigation is wired up within the individual screens of our StackNavigator. My ItemList screen maps over an array of items and creates a <TouchableOpacity /> for each item. Since ItemList is a direct child of the StackNavigator, it gets passed the props.navigation prop, which we can use to navigate to the other screens we’ve defined in the navigator. We’ll callprops.navigation.navigate('Item', { title: item.name }) which takes us to the Item screen:

If any of this looks unfamiliar check out my Up and Running post.

That’s all for the StackNavigator!


TabNavigator

The setup for TabNavigator looks a lot like the setup for the StackNavigator:

The main difference is that we’ll create an order property in our navigatorConfig object to tell the TabNavigator how to arrange the tabs from left to right. We’ll also set animationEnabled to true so we can see our tab transitions more clearly.

One quick note: I’m naming my routes with the same names as I’m giving my screens (i.e. the route named ‘TabA’ refers to the component I’ve imported and calledTabA) but you can name routes whatever you want.

We’re done setting up the TabNavigator. Simple!


DrawerNavigator

It’s finally time to combine everything we’ve done into something we can use!

We assigned our StackNavigator to a variable named Stack and our TabNavigator to a variable named Tabs. Remember when I said that the ‘screen’ a navigator renders can be a navigator in itself? We’ll see how that works now. Let’s create a DrawerNavigator (which should look pretty familiar at this point):

‘Stack’ and ‘Tabs’ are both Navigators. ‘Plain’ is a plain old component.

The DrawerNavigator has three routes: one named ‘Stack’, which holds our StackNavigator, one named ‘Tabs’, which holds our TabNavigator, and one named ‘Plain’ that holds our plain old component. We need to be sure to export Drawer from this file in order to use it in our index.ios.js.

Now to render our App. We’ll import our DrawerNavigator into index.ios.js and pass it to the AppRegistry. Here’s the entirety of that file:

import { Drawer } from './src/navigators'
import { AppRegistry } from 'react-native'
AppRegistry.registerComponent('RNNavigators', () => Drawer );

Let’s run this sucker!

Note: I’ve created a button in our Plain component with the label ‘Open Drawer’, which uses props.navigation (given to us by the DrawerNavigator), and passes it the special string ‘DrawerOpen’, which opens up the drawer for us without having to swipe from the edge of the screen.

Looking good!

Final Thoughts

Let’s make one final change to demonstrate just how composable navigators are. We’re going to use the StackNavigator we already made as a route in our TabNavigator. We only need to make one tiny change—we’ll replace TabC’s screen with our StackNavigator, which we’ve assigned to the variable Stack:

TabC is now our Stack

Our StackNavigator is now our third tab:

This pattern is used in many apps (i.e. most of Google’s apps) and feels pretty natural, but it’s actually a fairly complex nested structure — it’s a StackNavigator within a TabNavigator within a DrawerNavigator. Whoa!

To recap, we learned about the three different kinds of Navigators that React Navigation offers (Stack, Tab, Drawer), we learned how to initialize a navigator, and we saw that it’s possible for a ‘screen’ to be a navigator itself!

Find the final code here, and be sure to check out the other posts in this series!