How to Combine Bottom Tabs, Top Tabs and a Hamburger Menu in a React Native Application

by | Apr 10, 2019

Mixing different navigation components can be tricky to implement as well as suggested against.  But there may be scenarios where you need to mix a few different components to encapsulate the experience you are looking to convey.   This guide will walk you through creating a simple application that contains bottom tab navigation, top tabs, and a hamburger menu to give you the basics of mixing navigation components.

First let’s initialize the project:

react-native init navigationExample

cd navigationExample

Once initialized we will need to install some packages. The first package we will install is react-navigation.  This will give you the hamburger menu and the bottom tabs and as the name might suggest allow you to navigate between different screens.  Run the following commands:

npm install –save react-navigation

npm install –save react-navigation-gesture-handler

react-native link

Android requires one extra step. Make sure your MainActivity.java looks like this:

package com.navigationexample;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "navigationExample";
    }

  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
      @Override
      protected ReactRootView createRootView() {
       return new RNGestureHandlerEnabledRootView(MainActivity.this);
      }
    };
  }
}

 

Now that we have that we will install Native Base.  Native Base is a library that allows use to different ui components and allows for simple styling.  Running the following commands with install Native Base:

npm install native-base –save

react-native link native-base

We will also install an icon package for access to some icons for the hamburger menu and bottom tabs.

npm install react-native-vector-icons

react-native link react-native-vector-icons

Awesome, now that we have the packages we need lets get started on the actual code.  First let’s create a `src` folder to hold our screen files. Then let’s create a couple basic screens.

src/DefaultScreen.js

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import Icon from 'react-native-vector-icons/SimpleLineIcons';

const instructions = Platform.select({
 ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
 android:
   'Double tap R on your keyboard to reload,\n' +
   'Shake or press menu button for dev menu',
});

export default class DefaultScreen extends Component {
 render() {
   return (
     <View style={styles.container}>
       <Text style={styles.welcome}>Welcome to React Native!</Text>
       <Text style={styles.instructions}>To get started, edit App.js</Text>
       <Text style={styles.instructions}>{instructions}</Text>
     </View>
   );
 }
}

const styles = StyleSheet.create({
 container: {
   flex: 1,
   justifyContent: 'center',
   alignItems: 'center',
   backgroundColor: '#F5FCFF',
 },
 welcome: {
   fontSize: 20,
   textAlign: 'center',
   margin: 10,
 },
 instructions: {
   textAlign: 'center',
   color: '#333333',
   marginBottom: 5,
 },
});

 

This file is the basic screen that react sets up for you when you start a project.

src/BlueScreen.js

import React, {Component} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import Icon from 'react-native-vector-icons/SimpleLineIcons';


export default class BlueScreen extends Component {
    render() {
 	 return (
        <View style={styles.container}>
     	 <Text style={styles.title}>Blue Screen</Text>
        </View>
 	 );
    }
 }
  const styles = StyleSheet.create({
    container: {
 	 flex: 1,
 	 justifyContent: 'center',
 	 alignItems: 'center',
 	 backgroundColor: 'blue',
    },
    title: {
 	 fontSize: 20,
 	 textAlign: 'center',
 	 margin: 10,
    }
 });

Now that we have two screens we can add them to a Drawer Navigator.

src/HamburgerNavigation.js

import {
 createAppContainer,
 createDrawerNavigator,
} from 'react-navigation';
import BlueScreen from './BlueScreen';
import DefaultScreen from './DefaultScreen';

const HamburgerNavigation = createDrawerNavigator(
    {
        BlueScreen: BlueScreen,
        DefaultScreen: {
            screen: DefaultScreen,
        }
    },
    {
        initialRouteName: ‘DefaultScreen’,
        
        
    }
 );


export default createAppContainer(HamburgerNavigation);

You will then need to make a couple changes to you App.js to display the new screens.

App.js

import React, {Component} from 'react';
import HamburgerNav from './src/HamburgerNav'


export default class App extends Component {
 render() {
   return (
     <HamburgerNav/>
   );
 }
}

So now if you build your application with `react-native run-ios` or `react-native run-android` you should see the same default screen as you would if you started a react project from scratch.  The difference is if you swipe from left to right, you will see the drawer menu open. You can then open the BlueScreen. For now the drawer menu is only accessible from the swipe gesture we will allow the user to access it from the nav bar later.  The drawer navigator handles all the navigation to either screen which makes it easy to implement. Yet this isn’t the final implementation of the menu so more to come here. Now let’s get the bottom tabs in place.

src/BottomTabs.js

import React from 'react';
import { createStackNavigator, createBottomTabNavigator, createAppContainer } from "react-navigation";
import Icon from 'react-native-vector-icons/SimpleLineIcons';
import GreenScreen from "./GreenScreen";
import RedScreen from "./RedScreen";



const GreenTab = createStackNavigator({
    Green: GreenScreen
});

const RedTab = createStackNavigator({
    Red: RedScreen
});

const Tabs = createBottomTabNavigator({
    Green: GreenTab,
    Red: RedTab
}, {
    defaultNavigationOptions: ({ navigation }) => ({
        tabBarIcon: () => {
            const { routeName } = navigation.state;
            let tabName;
            tabName = routeName === 'Green' ? 'home' : 'grid';

            return <Icon name={tabName} size={20} />
        }
    })
});

export default createAppContainer(Tabs);

This file holds the logic for the bottom tab navigation.  Each tab has its own stack navigator that way if you wanted to add more screens on to one tab you could by simply adding them to the correct stack.  To set the icons for the specific tabs we use the tabBarIcon property within defaultNavigationOptions and set the icon based on the title of the tab.  You can do some more customizing of the tabs (active color, inactive color, padding, font etc.) with the tabBarOptions property. For now we are just sticking with the default tab styling.  For the sake of not showing too much of the same code GreenScreen and RedScreen are the same as the BlueScreen from earlier except for one minor difference.

import HamburgerIcon from './HamburgerIcon';

export default class GreenScreen extends Component {
    static navigationOptions = () => {
        return {
            headerLeft: <HamburgerIcon/>
        };
    };

In this snippet I created a hamburger icon to display on nav to allow access to drawer menu without the swipe gesture.  The hamburger icon was purely this custom component I created.

src/HamburgerIcon.js

import React, {Component} from 'react';
import { withNavigation } from 'react-navigation';
import { TouchableOpacity } from "react-native-gesture-handler";
import Icon from 'react-native-vector-icons/SimpleLineIcons';


class HamburgerIcon extends Component{
    render() {
 	 return (
        <TouchableOpacity
        style={{
            width: 44,
            height: 44,
            marginLeft: 20
        }}
        onPress={()=>{
            this.props.navigation.openDrawer();
        }}>
            <Icon name='menu' size={20} color='black'/>
        </TouchableOpacity>
 	 )
    };
}

export default withNavigation(HamburgerIcon);

 

We are almost there! We now have bottom tabs with a hamburger menu.  Last up is the top tabs using Native Base. For these we will just edit the GreenScreen render function to include the tabs.  

src/GreenScreen.js

import React, {Component} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import HamburgerIcon from './HamburgerIcon';
import { Container, Tab, Tabs, StyleProvider } from 'native-base';
import Tab3 from './Tab3.js';
import Tab2 from './Tab2.js';


export default class GreenScreen extends Component {
    static navigationOptions = () => {
        return {
            headerLeft: <HamburgerIcon/>
        };
    };
    render() {
        return (
            <Container>
                <Tabs>
                    <Tab
                    heading='Green Tab'>
                        <View style={styles.container}>
                            <Text style={styles.title}>Green Screen</Text>
                        </View>
                    </Tab>
                    <Tab heading='Tab 2'>
                        <Tab2 />
                    </Tab>
                    <Tab heading='Tab 3'>
                        <Tab3 />
                    </Tab>
                </Tabs>
            </Container>
        );
    }
 }
  const styles = StyleSheet.create({
    container: {
 	 flex: 1,
 	 justifyContent: 'center',
 	 alignItems: 'center',
 	 backgroundColor: 'green',
    },
    title: {
 	 fontSize: 20,
 	 textAlign: 'center',
 	 margin: 10,
    }
 });

 

First we imported the correct component we needed from Native Base and then updated the render function to include the tabs.  Within each tab we have added a couple components. For simplicity the Tab2 and Tab3 files are simply a component with a text within a view but you can make these looks however you want. If you wanted to you could create stack navigators for each of these tabs as well to allow you to open screens and still show the top tabs other wise you can use the stack navigator in which the GreenScreen already resides.  As mentioned earlier Native Base allows for easy customization of their components but for now we are sticking with the default. Check out the links at the bottom of the screen if you are interested in customizing Native Base components.

Now we are almost there!  A couple of things we need to clean up.  Number one we probably don’t want the tabs option actually showing in the drawer menu.  Number two need a back button for the screens with in the drawer menu. So for hiding the tabs from the drawer menu we will customize the content showing in the drawer menu using contentComponent property.  This allows us to customize exactly what shows in the drawer. We will just add two text components to access the two screens. You can do alot of cool thing with the ui of the drawer menu but this guide is focusing on the navigation structure. So let’s create a simple back button component to add to the BlueScreen and DefaultScreen.

src/BackButton.js

import React, { Component } from 'react';
import { StyleSheet } from 'react-native';
import { withNavigation } from 'react-navigation';
import { TouchableOpacity } from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
class BackButton extends Component {
 render() {
   return (
     <TouchableOpacity
       style={styles.backButton}
       onPress={() => {
         this.props.navigation.pop();
       }}
     >
       <Icon
         name= 'chevron-left'
         size={20}
       />
     </TouchableOpacity>
   );
 }
}
const styles = StyleSheet.create({
 backButton: {
   height: 44,
   width: 44,
   justifyContent: 'center',
   alignItems: 'center',
 },
});
export default withNavigation(BackButton);

This back button component is just an icon within a TouchableOpacity and onPress just pop the screen the user is on from the stack.  Then within the BlueScreen and DefaultScreen import the back button and add this snippet.

static navigationOptions = () => {
        return {
            headerLeft: <BackButton/>
        };
 };

Last but not least we need to update the HamburgerNav.

src/HamburgerNav

import React from 'react';
import {
 createAppContainer,
 createDrawerNavigator,
 createStackNavigator,
} from 'react-navigation';
import { ScrollView } from 'react-native-gesture-handler';
import { SafeAreaView } from 'react-navigation';
import { Text } from 'react-native';
import BlueScreen from './BlueScreen';
import DefaultScreen from './DefaultScreen';
import BottomTabs from './BottomTabs';


const HamburgerNavigation = createDrawerNavigator(
    {
        Tabs: BottomTabs,
    },
    {
        initialRouteName: 'Tabs',
        contentComponent: props => {
            return (
                <ScrollView>
                    <SafeAreaView
                    forceInset={{ top: 'always', horizontal: 'never' }}
                >
                    <Text
                        onPress={() => {
                        props.navigation.navigate('BlueScreen');
                        props.navigation.closeDrawer();
                        }}
                    >
                        BlueScreen
                    </Text>
                    <Text
                        onPress={() => {
                        props.navigation.navigate('DefaultScreen');
                        props.navigation.closeDrawer();
                        }}
                    >
                        DefaultScreen
                    </Text>
                    </SafeAreaView>
                </ScrollView>
            )
        }
    }
 );

 const Stack = createStackNavigator(
 	 {
        Drawer: {
            screen: HamburgerNavigation,
            navigationOptions: {
                header: null,
            },
     	 },
        BlueScreen: BlueScreen,
        DefaultScreen: {
            screen: DefaultScreen,
        }
 	 }
 );


export default createAppContainer(Stack);

Here you can see the BlueScreen and Default screen now aren’t inside of the DrawerNavigator.  We add these two screens to StackNavigator that also contains the DrawerNavigator. This way these screens are opened on top of the drawer navigator. This is all made possible by the contentComponent property on the DrawerNavigator, this allows us to make custom content for the menu and use the stack navigator to navigate to these pages.  Obviously in this example we made the drawer menu very simple in design but React Native gives you the tools to customize how you would like.

There you have it! A simple React Native application with bottom tab navigation, top tabs, and a hamburger menu.  No matter what direction you would like to take with navigation inside you app React Native gives you the freedom and the ability to create the experience you want.  See the finished product below! For more information on the packages I used check the links below.

 

Caleb Hatcher

Caleb Hatcher

Mobile software engineer with experience in cross-platform solutions such as react-native, flutter, and titanium. Graduated from Randolph Macon in 2016 with a major in Computer Science and a minor in Biology. Started the Shockoe indoor soccer team.