React Navigation navigation Tutorial for beginners | Custom Tab and custom drawer Navigators
In this article, I am going to introduce a custom side menu drawer and bottom tab UI. Along with learning the awesome side menu drawer and bottom tabs animation UI implementation in React Native, we will also learn how its coding workflows and structures work.
Set-up and Installation
We are using the latest React navigation 6 for switching between the screens. To use this, we have to do some installation. So, let’s install that.
For Navigation
npm install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs @react-navigation/drawer
npm install react-native-screens react-native-safe-area-context
or
yarn add @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs @react-navigation/drawer
yarn add react-native-screens react-native-safe-area-context
After installing dependencies run this command for iOS:
npx pod-install
Initiate Navigation in React Native
In this step, we will configure routes in our React native app. In order to enable navigation, we need to create home, profile, notification, and setting screens.
We are all set with the basic React native project, now we have to create the four screens.
mkdir screens
touch screens/home/home.tsx
touch screens/notification/notification.tsx
touch screens/profile/profile.tsx touch screens/setting/setting.tsx
After adding code homeScreen, notificationScreen, profileScreen and settingScreen file
homeScreen.tsx
/* eslint-disable prettier/prettier */ import { SafeAreaView, Text, View } from "react-native"; import React from 'react'; import Icon from 'react-native-vector-icons/Ionicons'; function HomeScreen() { return ( <SafeAreaView style={{flex:1}}> <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor:'#3BB9FF' }}> <Text>Home Screen</Text> <Icon name={'ios-home'} size={22} color={'#000'} /> </View> </SafeAreaView> ); } export default HomeScreen;
notificationScreen.tsx
/* eslint-disable prettier/prettier */ import { SafeAreaView, Text, View } from "react-native"; import React from 'react'; function NotificationScreen() { return ( <SafeAreaView style={{flex:1}}> <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor:'#B666D2' }}> <Text>Ntification Screen</Text> </View> </SafeAreaView> ); } export default NotificationScreen;
profileScreen.tsx
/* eslint-disable prettier/prettier */ import { SafeAreaView, Text, View } from "react-native"; import React from 'react'; function ProfileScreen() { return ( <SafeAreaView style={{flex:1}}> <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor:'#007C80' }}> <Text>Profile Screen</Text> </View> </SafeAreaView> ); } export default ProfileScreen;
settingScreen.tsx
/* eslint-disable prettier/prettier */ import { SafeAreaView, Text, View } from "react-native"; import React from 'react'; function SettingScreen() { return ( <SafeAreaView style={{flex:1}}> <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor:'#387C44' }}> <Text>Setting Screen</Text> </View> </SafeAreaView> ); } export default SettingScreen;
/* eslint-disable prettier/prettier */ /** * Sample React Native App * https://github.com/facebook/react-native * * @format */ import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import React from 'react'; import 'react-native-gesture-handler'; import { LogBox, SafeAreaView, useColorScheme, } from 'react-native'; import { Colors, } from 'react-native/Libraries/NewAppScreen'; import BottomTabNavigator from './navigations/bottomTabNavigator'; import DrawerNavigator from './navigations/drawerNavigator'; const Stack = createNativeStackNavigator(); LogBox.ignoreLogs(['Warning: ...']); // Ignore log notification by message LogBox.ignoreAllLogs(); function App(): JSX.Element { const isDarkMode = useColorScheme() === 'dark'; const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, flex: 1, }; return ( <SafeAreaView style={backgroundStyle}> <NavigationContainer> <Stack.Navigator initialRouteName='Home' screenOptions={{headerShown:false}}> <Stack.Screen name="Home" component={DrawerNavigator} /> </Stack.Navigator> </NavigationContainer> </SafeAreaView> ); } export default App;
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable prettier/prettier */ import React from "react"; import { createDrawerNavigator } from '@react-navigation/drawer'; import BottomTabNavigator from "./bottomTabNavigator"; import Icon from "react-native-vector-icons/Ionicons"; import CustomDrawer from "../components/customDrawer"; import ProfileScreen from "../screens/profile/profileScreen"; import NotificationScreen from "../screens/notification/notificationScreen"; import SettingScreen from "../screens/setting/settingScreen"; const Drawer = createDrawerNavigator(); const DrawerNavigator = () => { return( <Drawer.Navigator drawerContent={props => <CustomDrawer {...props} />} screenOptions={{ headerShown: false, drawerActiveBackgroundColor: "#B666D2", drawerActiveTintColor: "#fff", drawerLabelStyle: { marginLeft: -20, }, }}> <Drawer.Screen name={"Home Drawer"} initialParams={{ params: 'feed' }} component={BottomTabNavigator} options={{ title:"Feed", drawerIcon : ({focused, color, size}) =>( <Icon name="home-sharp" size={18} color={color} /> ) }} /> <Drawer.Screen name={"Profile"} initialParams={{ params: 'Profile' }} component={BottomTabNavigator} options={{ title:"Profile", drawerIcon : ({focused, color, size}) =>( <Icon name="ios-person-circle-sharp" size={18} color={color} /> ) }} /> <Drawer.Screen name={"Notification"} initialParams={{ params: 'Notification' }} component={BottomTabNavigator} options={{ title:"Notification", drawerIcon : ({focused, color, size}) =>( <Icon name="ios-notifications-circle" size={18} color={color} /> ) }} /> <Drawer.Screen name={"Settings"} initialParams={{ params: 'Settings' }} component={BottomTabNavigator} options={{ title:"Settings", drawerIcon : ({focused, color, size}) =>( <Icon name="settings-sharp" size={18} color={color} /> ) }} /> </Drawer.Navigator> ) } export default DrawerNavigator;
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable react/no-unstable-nested-components */ /* eslint-disable prettier/prettier */ import React from "react"; import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import HomeScreen from "../screens/home/homeScreen"; import ProfileScreen from "../screens/profile/profileScreen"; import NotificationScreen from "../screens/notification/notificationScreen"; import SettingScreen from "../screens/setting/settingScreen"; import Icon from "react-native-vector-icons/Ionicons"; import { Platform, StyleSheet, TouchableOpacity } from "react-native"; import CustomTabBarButton from "../components/customTabBarButton"; import CustomTabBar from "../components/customTabBar"; import { useNavigation } from "@react-navigation/native"; Icon.loadFont(); const Tab = createBottomTabNavigator(); function BottomTabNavigator(props) { const navigation = useNavigation(); console.log("navigation", props) return ( <Tab.Navigator initialRouteName={props.route && props.route.params && props.route.params.params?props.route.params.params:'feed'} tabBar={props => <CustomTabBar {...props} />} screenOptions={(route)=>({ headerShown:false, tabBarShowLabel:false, tabBarStyle:styles.tabBarStyle, tabBarActiveTintColor:"#B666D2", tabBarInactiveTintColor:"#c1c1c1", tabBarIcon:({color, size, focused}:any)=>{ let iconName; if(route.route.name === "feed"){ iconName = focused ? "ios-home-sharp" : "ios-home-outline"; }else if(route.route.name === "Profile"){ iconName = focused ? "ios-person-circle-sharp" : "ios-person-circle-outline"; }else if(route.route.name === "Notification"){ iconName = focused ? "ios-notifications-circle" : "ios-notifications-circle-outline"; }else if(route.route.name === "Settings"){ iconName = focused ? "settings-sharp" : "ios-settings-outline"; } return <Icon name={iconName} size={22} color={color} /> // eslint-disable-next-line comma-dangle } })}> <Tab.Screen name="feed" component={HomeScreen} options={{ tabBarLabel:"Feeds", title:"Feed", headerShown:true, tabBarButton: props => (<CustomTabBarButton route="feed" {...props} />), headerLeft:() =>{ return ( <TouchableOpacity style={{paddingLeft:10}} onPress={() => navigation.openDrawer()}> <Icon name={Platform.OS === 'ios' ? 'ios-menu': 'md-menu'} size={30} color={'#222'} style={{marginRight:10}} /> </TouchableOpacity> ) } }} /> <Tab.Screen name="Profile" component={ProfileScreen} options={{ tabBarLabel:"Profile", title:"Profile", headerShown:true, tabBarButton: props => <CustomTabBarButton {...props} />, headerLeft:() =>{ return ( <TouchableOpacity style={{paddingLeft:10}} onPress={() => navigation.openDrawer()}> <Icon name={Platform.OS === 'ios' ? 'ios-menu': 'md-menu'} size={30} color={'#222'} style={{marginRight:10}} /> </TouchableOpacity> ) } }} /> <Tab.Screen name="Notification" component={NotificationScreen} options={{ tabBarLabel:"Notification", title:"Notification", headerShown:true, tabBarButton: props => <CustomTabBarButton {...props} />, headerLeft:() =>{ return ( <TouchableOpacity style={{paddingLeft:10}} onPress={() => navigation.openDrawer()}> <Icon name={Platform.OS === 'ios' ? 'ios-menu': 'md-menu'} size={30} color={'#222'} style={{marginRight:10}} /> </TouchableOpacity> ) } }} /> <Tab.Screen name="Settings" component={SettingScreen} options={{ tabBarLabel: "Settings", title:"Settings", headerShown:true, tabBarButton: props => <CustomTabBarButton {...props} />, headerLeft:() =>{ return ( <TouchableOpacity style={{paddingLeft:10}} onPress={() => navigation.openDrawer()}> <Icon name={Platform.OS === 'ios' ? 'ios-menu': 'md-menu'} size={30} color={'#222'} style={{marginRight:10}} /> </TouchableOpacity> ) } }} /> </Tab.Navigator> ); } export default BottomTabNavigator; const styles = StyleSheet.create({ tabBarStyle:{ backgroundColor:'transparent', position:'absolute', borderTopWidth:0, bottom:15, right:0, left:0, height:55, } });
After Creating a components folder inner the src folder and creating the customDrawer.tsx,customTabBar.tsx and customTabBarButton.tsx file and add the following code:
/* eslint-disable prettier/prettier */ import { DrawerContentScrollView, DrawerItemList } from "@react-navigation/drawer"; import React from "react"; import { Dimensions, Image, ImageBackground, StyleSheet, View } from "react-native"; const {width} = Dimensions.get('screen'); const CustomDrawer = props =>{ return ( <DrawerContentScrollView {...props} contentContainerStyle={{flex: 1}}> <ImageBackground source={require("../../assets/images/julen-rey-azcona-S5I_EpHhXio-unsplash.jpg")} style={{height:140}}> <Image source={require("../../assets/images/camera-7726802.jpg")} style={styles.userImg} /> </ImageBackground> <View style={styles.drawerListWrapper}> <DrawerItemList {...props} /> </View> </DrawerContentScrollView> ) } export default CustomDrawer; const styles = StyleSheet.create({ userImg:{ width:110, height:110, borderRadius:110/2, position:'absolute', left:width/2-130, bottom:-110/2, borderWidth:4, borderColor:"#fff" }, drawerListWrapper:{ marginTop:65 } });
/* eslint-disable prettier/prettier */ import { BottomTabBar } from '@react-navigation/bottom-tabs'; import React from 'react'; import { StyleSheet, View } from 'react-native'; const CustomTabBar = props => { return ( <View> <View style={styles.tabBar}> <BottomTabBar {...props} /> </View> </View> ); } export default CustomTabBar; const styles = StyleSheet.create({ tabBar : { position:'absolute', right:10, left:10, bottom:15, height:30, backgroundColor:"#fff", borderRadius:10, shadowColor:'#222', shadowOffset:{ width:0, height:1, }, shadowOpacity:0.25, shadowRadius:2, elevation:3, // width: "100%", margin:0 } });
/* eslint-disable prettier/prettier */ /* eslint-disable @typescript-eslint/no-unused-vars */ import React from 'react'; import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; import Svg, {Path} from 'react-native-svg'; const CustomTabBarButton = props => { const {route, children, accessibilityState, onPress} = props; if (accessibilityState.selected) { return ( <View style={styles.btnWrapper}> <View style={{flexDirection: 'row'}}> <View style={[ styles.svgGapFiller, { borderTopLeftRadius: route === 'home' ? 10 : 0, }, ]} /> <Svg width={71} height={58} viewBox="0 0 75 61"> <Path d="M75.2 0v61H0V0c4.1 0 7.4 3.1 7.9 7.1C10 21.7 22.5 33 37.7 33c15.2 0 27.7-11.3 29.7-25.9.5-4 3.9-7.1 7.9-7.1h-.1z" fill={"#FFF"} /> </Svg> <View style={[ styles.svgGapFiller, { borderTopRightRadius: route === 'settings' ? 10 : 0, }, ]} /> </View> <TouchableOpacity activeOpacity={1} onPress={onPress} style={[styles.activeBtn]}> <Text>{children}</Text> </TouchableOpacity> </View> ); } else { return ( <TouchableOpacity activeOpacity={1} onPress={onPress} style={[ styles.inactiveBtn, { borderTopLeftRadius: route === 'home' ? 10 : 0, borderTopRightRadius: route === 'settings' ? 10 : 0, }, ]}> <Text>{children}</Text> </TouchableOpacity> ); } }; export default CustomTabBarButton; const styles = StyleSheet.create({ btnWrapper: { flex: 1, alignItems: 'center', }, activeBtn: { flex: 1, position: 'absolute', top: -22, width: 50, height: 50, borderRadius: 50 / 2, backgroundColor: "#FFF", alignItems: 'center', justifyContent: 'center', paddingTop: 5, }, inactiveBtn: { flex: 1, backgroundColor: "#FFF", justifyContent: 'center', alignItems: 'center', }, svgGapFiller: { flex: 1, backgroundColor: "#FFF", }, });