Celsius' Notes
Hire me as a freelancer for your mobile (iOS native, cross-platform with React Native), web and backend software needs.
Go to portfolio/resumé
6 min read

How to make a drawer navigator with react-native-navigation

In this tutorial we will build a drawer navigation for an imaginary e-commerce app with the React Native Navigation (RNN) library.
The first image below is what the finished drawer navigation will look like. The second one shows dribble shot that I used as design inspiration.

output

About React Native Navigation

React Native Navigation is a native navigation library based on the underlying native navigation of the platform and is not a JavaScript based navigation solution like most other navigation libraries for React Native.

Installing React Native Navigation

To install React Native Navigation simply follow the installation instructions give here for Android or here for iOS.
Note, that this article was written using React Native Navigation version 1!

Getting Started

To start an app with React Native Navigation you have to call either the static startTabBasedAppor the static startSingleScreenApp with the appropriate params object. We will be using the startTabBasedApp method for our drawer navigator example, since it naturally comes with support for tabs.

Simple Tab

To keep this simple and focused on the the drawer menu, we will be showing empty screens for every tab, with nothing but the screen title in the navigation bar. Let's create a Tab component right in our index.js file which will serve as the screen component to be shown for all drawer tabs.

const Tab = () => {
	return(
		<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
			<Text>Tab Screen</Text>
		</View>
	)
}

Registering Screens

RNN requires you to register screens before showing them on screen. For that we create a registerScreens() function.

In that function we will register the components for all the screens we want to show on in our app. The RNN docs recommend choosing names which will be unique within the entire app, as your screens might end up being bundled with other packages. It is recommended to use the following naming convention packageName.screenName. So let's create a constant packageName and the registerScreens() function in our index.js file.

import { Navigation } from 'react-native-navigation';

const packageName = 'com.rnplayground';

function registerScreens(){
	Navigation.registerComponent(`${packageName}.Cart`, () => Tab );
	Navigation.registerComponent(`${packageName}.Wishlist`, () => Tab );
	Navigation.registerComponent(`${packageName}.Deals`, () => Tab );
	Navigation.registerComponent(`${packageName}.Browse`, () => Tab );
	Navigation.registerComponent(`${packageName}.SettingsTab`, () => Tab );
	Navigation.registerComponent(`${packageName}.DrawerScreen`, () => DrawerNavigatorContainer );
}

Defining Tabs

The Navigation.startTabBasedApp method takes an object which has a tabs property, which excpects an array specifying all the tabs and their properties.

Let's create a function called getTabConfig() which returns the this tabs array.

function getTabConfig() {
	const tabs = [
		{
			screen: `${packageName}.Cart`,
			title: 'Shopping Cart',
			iconName: 'shopping-cart',
			iconColor: 'yellow',
			icon: require('./image.png'),
			navigatorStyle: {
				tabBarHidden: true,
			},
		},
		{
			screen: `${packageName}.Wishlist`,
			title: 'Wishlist',
			iconName: 'favorite',
			iconColor: 'red',
			icon: require('./image.png'),
			navigatorStyle: {
				tabBarHidden: true,
			},

		},
		{
			screen: `${packageName}.Deals`,
			title: 'Deals',
			iconName: 'money-off',
			iconColor: 'orange',
			icon: require('./image.png'),
			navigatorStyle: {
				tabBarHidden: true,
			},

		},
		{
			screen: `${packageName}.Browse`,
			title: 'Browse',
			iconName: 'search',
			iconColor: 'green',
			icon: require('./image.png'),
			divider: true,
			navigatorStyle: {
				tabBarHidden: true,
			},
		},
		{
			screen: `${packageName}.SettingsTab`,
			title: 'Settings',
			iconName: 'settings',
			iconColor: 'grey',
			icon: require('./image.png'),
			navigatorStyle: {
				tabBarHidden: true,
			},
		},
	];

	return tabs;
}

If you take a look at the documentation for startTabBasedApp here, you'll notice that the iconNameiconColor and the divider (in the Browse tab object) properties are nowhere to be found. That's because they are not part of RNN. I just added those myself, because we'll need those values later on.

The screen property specifies which component will be shown when you tap on that tab. The title property defines the name that will show up in the navigation bar. While we won't be using the icon attribute, it is required by RNN for every tab object. We just give it the value of a random image.png image, which is in the same directory as the index.js file.

The navigatorStyle object allows you to style your navigator. We only make use of the tabBarHidden attribute for now, by setting it to true in every tab.

Starting the App

Now that we've defined our tabs it's almost time to start the app for the first time. We're close, I promise. Let's create a function called startApp() in the index.js file in which we register the screens, get the tab configurations and then call Navigator.startTabBasedApp(), passing the tabs and a few other options.

The drawer property let's us specify a drawer with all its properties and settings. We want a left drawer, which is why we define the left property of the drawer object. In it we define the screen, which will be rendered as the left drawer and also get to define props that will get passed to the drawer when the app is started. The contents of the passProps object will be available as regular props in the drawer component. We pass the tabs array, an activeIndex attribute set to 0 and the profileInfo object.

In order to make the tab bar not show at the bottom of the screen, we also set the tabBarHidden property of the tabStyle object in the drawer object to true.

function startApp() {
  
	registerScreens();
    
	const tabs = getTabConfig();

	const profileInfo = {
		firstName: 'Celsius',
		lastName: 'W.',
		imgSource: require('./image.png')
	}
    
	Navigation.startTabBasedApp(
		{
		tabs: tabs,
		drawer: { 
			left: {
			screen: `${packageName}.DrawerScreen`, // unique ID registered with Navigation.registerScreen
			passProps: {
				tabs: tabs,
				activeTabIndex: 0,
				profileInfo: profileInfo

			},
			fixedWidth: 800,
			}
		},
		tabsStyle:{
			tabBarHidden: true,
		}
		},
	);
  
  }

DRAWERNAVIGATOR CLASS

Before we move on to the DrawerNavigatorContainer, the centerpiece of this tutorial, let's first take a look at two other components that we will use inside the DrawerNavigatorContainer: the DrawerProfileContainer and the DrawerItem component.

The DrawerProfileContainer component houses the profile image and the name of the user. The DrawerItem component is a pressable component representing all the tabs that we can navigate to. It has a icon and a label.

export default DrawerProfileContainer = (props) => {

    const {source, firstName, lastName, containerStyle} = props;
    const iconProps = source ? {source: source} : {icon: {name: 'account-circle'}}

    return(
        <View style={[styles.container, containerStyle]}>
            <Avatar
                width={80}
                height={80}
                activeOpacity={0.7}	
                rounded={true}
                containerStyle={styles.avatar}
                {...iconProps}
            />
            <Text
                style={styles.nameText}>
                {firstName} {lastName}
            </Text>
        </View>
    );


}

export default DrawerItem = (props) => {
    const {label, iconName, iconColor, isActive} = props;
    const containerStyles = isActive ? [styles.container, styles.containerActive] : [styles.container]
    return (
        <TouchableOpacity onPress={props.onPress}
            underlayColor='blue'>
            <View style={containerStyles}>
                {isActive && 
                    <View style={styles.leftBar}/>
                }

                <Icon name={iconName}
                    color={iconColor || 'white'}
                    size={24}
                    containerStyle={styles.icon}
                />
                <Text style={styles.label}>
                    {label}
                </Text>
            </View>
        </TouchableOpacity>
    );
}

Following is the code for the DrawerNavigationContainer component. This is the component that describes the look and behavior of the drawer.

In the render() method we return DrawerProfileContainer. We also iterate over the tabs prop that we passed via the passProps when we called Navigation.startTabBasedApp() above. From each item in that array we create a DrawerItem element, passing the navigationItemPressed() method as the onPress prop. When a DrawerItem receives a press, the navigator is told to switch to the tab specified by the DrawerItems's index and then toggle the drawer to close it. We also have to set our activeTabIndex state to the new index, which is exactly what we do.

You'll notice that there is also a Divider component that we render after the DrawerItem if the divider attribute on the current tab is a truthy value. What gives? If you take a look at the 4th tab in the tabs array in getTabConfig you'll see that is has a divider property. This is not a property provided by React Native Navigation, but one that I added so that when I rendered the drawer items, I could tell the DrawerNavigationContainer to render a divider after every DrawerItem component associated with a tab with a truthy divider property.

While we're on the subject of properties not provided by React Native Navigation, it should also be mentioned that the iconColor and iconName properties are also not from React Native Navigation. I added those, so that I could tell the DrawerItems which icons to render in which colors next to the labels. The icons are material icons from the react-native-elements library.

export default class DrawerNavigationContainer extends React.Component {

        state = {
            activeTabIndex: 0
        }

        navigationItemPressed = (route, index) => {
            action(`Should go to screen ${route.label} and close drawer.`)

            this.props.navigator.switchToTab({
                tabIndex: index,
            });
            this.props.navigator.toggleDrawer({
                side: 'left',
                to: 'closed',
                animated: 'true',
            });

            this.setState({
                activeTabIndex: index
            });
        }

        render(){
            const {tabs, profileInfo} = this.props;
            const {imgSource, ...rest} = profileInfo;

            if (!tabs) {
                return null;
            } else {
                return (
                    <View style={styles.container}>

                        <DrawerProfileContainer
                            source={imgSource}
                            {...rest}

                        />

                        <View style={styles.navigationContainer}>
                            {tabs.map((route, index) => {
                                const navigationLabel = route.title;
                                const navigationIconName = route.iconName;
                                const iconColor = route.iconColor;

                                return (
                                    <React.Fragment key={route.screen}>
                                        <DrawerItem
                                            label={navigationLabel}
                                            iconName={navigationIconName}
                                            isActive={this.state.activeTabIndex === index}
                                            iconColor={iconColor}
                                            onPress={() => {this.navigationItemPressed(route, index)}}
                                        >
                                        </DrawerItem>
                                        {route.divider && <Divider style={styles.divider} /> }
                                    </React.Fragment>
                                );
                            })}
                        </View>
                    </View>


                );
            }	

        }
    }

That's it. You should now have a drawer navigator if you compile and run the code on a device or an emulator.

You can check out the code for this article on github here.

If you have any questions or comments, get in touch via twitter or shoot me an email.