This is part 2 of a 2-part react navigation tutorial Combining Drawer, Tab and Stack navigators in React Navigation 6. If you haven’t read that yet, please read it first
here
Implementing navigation such that the Drawer and Tab navigators are visible in every screen isn’t a simple task. Simply put, the react navigation library is not designed in a way that this functionality is ready out-of-the-box.
When working with nested navigators, the navigation UI of the child navigator is present only in the screens which it contains. Because of this, in order to have BottomTabNavigator in every screen, it must contain every screen.
Since TabNavigator will contain all of our stacks, the only screen present in DrawerNavigator now becomes TabNavigator. But we still want to render ‘Home’, ‘My Rewards’ and ‘Location’ routes in the drawer. We’ll refactor CustomDrawerContent to render a custom list of items. To get the focused route, we’ll use a reference to the navigation object defined in App.js. Let’s begin!
Route items
For each screen we’ll have a configuration object that we’ll store in an array. Remember that TabNavigator is a screen as well, contained within DrawerNavigator as a Drawer.Screen:
navigation/RouteItems.js
Regardless of the navigation style I always use screens and routes to have a centralized place to make changes. Let’s jump to BottomTabNavigator:
BottomTabNavigator.js
We’ve added ‘MyRewardsStack’ and ‘LocationsStack’ as tab screens. Only routes with showInTab: true will render a tab. If you comment-out the if (!item.showInTab) section, you’ll get all of the tabs rendered:
With the full code, the page looks the same as before:
Note also that now the screen names aren’t hardcoded, we’re using the screens object to supply the names.
Let’s jump to DrawerNavigator:
DrawerNavigator.js
Now we’ve removed ‘MyRewardsStack’ and ‘LocationsStack’, and are rendering selected routes (in the previous code we rendered all of the Drawer.Screens, which in this case would be only HomeTabs screen). We have an issue right now - the focused check will not work since props.state.index will always return 0, we’re always in BottomTabNavigator screen:
As a workaround, we need to find out the current route, and we’ll do that using a reference to the navigation object.
App.js
We’re sending this reference as a prop to DrawerNavigator where we can use it to check the focused route:
DrawerNavigator.js
In the first render the getCurrentRoute() will return undefined, in that case we know that the focused route is HomeStack. We then, for each Drawer route, check if its name matches the focusedRouteItem.focusedRoute. For example, if we are on the MyRewards screen (or any other screen we would define in that stack), its focusedRoute would be MyRewardsStack. We get the desired result:
Conclusion
Using react navigation, we have implemented Drawer, Tab and Stack navigation such that the drawer and bottom tab UI is visible in every app route. We’ve added custom styles and components for Tabs, Headers and Drawer Items. We have also centralised our configuration for each route.
What’s next?
For further customisation, you can start by exploring the screenOptions and options props. Perhaps add a HeaderRight component to Drawer’s screenOptions, or add a tabBarBadge to Tab Navigators screen options.
When adding a new screen to any stack (or adding a new stack) make sure to add that screen’s config to routes to make sure our navigators access all of the required information. Happy coding!
Today we will be making use of the Drawer, Tab and Stack navigators. We will cover two cases:
a simpler scenario where we utilize the Tab Navigator in a single Drawer route
a more complicated flow where we want the Tab bar to be visible and accessible inside all our Drawer routes.
In this second example, we will try to overcome a design restriction of React Navigation - the different Navigators, if used together, can only be nested inside one another, and therefore can’t be intertwined.
Introduction
Adding navigation to a React Native application is greatly helped by using React Navigation library. It provides different types of navigators, with plenty of customization power. In some simple cases we can get by with using just one navigator, but often times we are presented with a challenge to combine multiple types in an app.
The example chosen is to build an app for a Hotel chain. Some of the features include booking a room at one of the hotels, browsing the different locations and using reward points. Here is a preview of what we will be building:
We can see right away the use of Drawer and Tab navigators. We will also implement each of the routes as a Stack Navigator, since we now that, for instance, the Book flow will contain multiple screens.
Getting started
(if this is your first React Native project, please read the official getting started guide before continuing)
Let’s initialize a new project. In your terminal, navigate to an empty directory and run the following command:
If you’re developing for IOS, you also need to install the pods:
$ cd ios; npx pod install; cd ..
Replace the contents of your App.js file with the following code:
Stack and Drawer navigators
Now we can go about adding the different navigators to our app. Remember, for this first example we want the DrawerNavigator to be the main (always visible) navigator in our app, with the BottomTabNavigator visible if the Home route is focused in the Drawer. Let’s begin by adding the following file structure in our project (all the files remain empty for now):
You can download the hotel_logo from the github repo provided at the end of this tutorial, or use your own. Next, we will create our Drawer Navigator that contains three routes (our Stack Navigators). For now, the stacks will contain a single screen defined directly in the stack file. In a real app, the stack can contain many screens, but it’s important to have at least one. Following are the contents of the stack files:
HomeStackNavigator.js:
MyRewardsStackNavigator.js:
LocationsStackNavigator.js:
We will explain the screenOptions in a moment. Now that we have defined our drawer stack navigators, we can create the DrawerNavigator:
DrawerNavigator.js:
And add it to our NavigationContainer in App.js
Let's run our code to see the results so far. Run
$ npx react-native start
to start the Metro bundler. Then, in a separate terminal, run
$ npx react-native run-android
or
$ npx react-native run-ios
depending on which platform you're developing for (run both one after the other if you want to simultaneously work on both platforms).
We can see the result now. We have React Navigation’s default header, an icon to open the drawer, and our stacks in the drawer menu. We can navigate freely between those stacks.
Now let’s circle back to the screenOptions we defined in the stack navigators. Try setting headerShown: true in HomeStackNavigator and observe what happens:
The Home component’s header is rendered below the Drawer Navigator’s. This is because the parent navigator’s UI is rendered on top of child navigator. Since we obviously want only one header, specifying headerShown: false for each of the stack navigator’s screenOptions hides the default stack header. Note that the title displayed in the drawer header is HomeStack, not Home. If we were to navigate to another screen in HomeStack, the title would not change. Could we have kept the Stack header and hidden the Drawer header? Yes! But for now, we want the default Drawer header as it provides us with an easy way to open the drawer - by pressing the menu icon in the header.
Tab navigator
We have added Drawer navigation to our app, and defined stack navigators with screens to add to our drawer menu. Now we need to add tab navigation to our Home Route. Firstly, lets define Book and Contact stack navigators in the same way as before:
BookStackNavigator.js:
ContactStackNavigator.js:
Now let’s create our Tab Navigator.
BottomTabNavigator
Notice how the first tab screen we added is the HomeStack, which we have already added in DrawerNavigator. In fact, you can think of BottomTabNavigator as a container of stacks, with the initial stack being HomeStack. Since in HomeStack we have a Home screen, the initial screen being rendered in the Tab navigator is the Home screen. And because we want to show this when the user is on the Home route in the drawer navigation, we will simply replace the HomeStackNavigator component in DrawerNavigator with BottomTabNavigator:
DrawerNavigator.js:
Let’s look at what we get:
When we are in the first route in DrawerNavigator, we can see the bottom tabs and navigate between them. If we move to another route in the Drawer, the tabs are no longer visible (since the tab navigator is just one of the drawer screens). We have again used headerShown: false to avoid rendering a double header.
Header and Tab design
We have implemented all our stacks, now we want to implement a few common requirements. Firstly, let’s add icons to our tabs. For this project we will use the react-native-vector-icons package to access FontAwesome icons. The full installation guide can be found
here
. Once the installation process is complete, we can edit our BottomTabNavigator.js as follows:
For each stack we have specified an icon and a tab label. tabBarIcon receives the focused prop, which we can use to highlight the current route (tabBarLabel can also receive this prop). There are many possibilities with options and screenOptions properties, some of which are explored at https://reactnavigation.org/docs/screen-options/. Let’s use screenOptions in Drawer Navigator to change the header and route names in the drawer menu:
DrawerNavigator.js:
Let’s breakdown all of the changes. First of all, looking at the Drawer Screens, we can change the header of each drawer item separately. You might not want to display a title when the user is in the Tab navigator, but maybe show the company’s logo instead. The headerTitle prop accepts a string as well as a function - giving us a lot of possibilities for customisation. Furthermore, the title shown in the header can be different than the one shown in the drawer menu.
Next, we want to change the look of the header to fit better with our client’s brand. We can do this by passing a function to DrawerNavigator’s screenOptions and specifying header style and components. ScreenOptions also receives the route prop. We are passing a function to headerLeft that renders our menu icon, and toggles the drawer - this toggle function is available in the navigation object.
Finally, let’s customise the Drawer menu. We only want to change the route item styles for now, and unfortunately there isn’t a simple DrawerNavigation prop that enables us to do this. Instead, we must pass a custom drawerContent function that enables us to render a completely custom component for each item. We are using the passed props to iterate through these items, but we could also render more routes using <DrawerItem>, or add an image component at the top of <DrawerContentScrollView>, or any number of other options.
Conclusion
In this tutorial, we have combined Drawer, Tab and Stack navigators to create a simple navigation flow. We have then, through screenOptions, supplied customization to get the look and feel we needed. In the next section, we will explore the problem of having both the Drawer and Tab navigations always visible and connected.
At DEVersity, we have two aims: to offer the best and most diverse range of software solutions for your business, and to provide an outstanding working environment in which the most talented developers can thrive. This is our blog.