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.
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 startTabBasedApp
or 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 iconName
, iconColor
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.