How to add React-Navigation Drawer menu to my screens? - react-native

I have a home screen built with React-Navigation 3.x. I have a header, some navigation icons, and a bottom tab menu. It is working well, but now I want to add a drawer menu to the screen and add an icon at the top right corner to toggle the drawer.
Here's a simplified version of my home screen (App.js):
import { createStackNavigator, createAppContainer, createBottomTabNavigator } from 'react-navigation';
import Activity1 from './Activity/Activity1';
import Activity2 from './Activity/Activity2';
import Activity3 from './Activity/Activity3';
import Calendar from './Screens/Calendar';
import Graph from './Screens/Graph';
import DrawerMenu from './components/DrawerMenu';
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Home',
headerRight: (<Button onPress={() => this.props.navigation.toggleDrawer()} title='Menu' />)
};
render() {
return (
<View style={styles.container}>
<View style={styles.iconContainer}>
<Icon name='icon1' onPress={this.navToActivity1} />
<Icon name='icon2' onPress={this.navToActivity2} />
<Icon name='icon3' onPress={this.navToActivity3} />
</View>
<View>
<DrawerMenu />
</View>
</View>
);
}
}
//create my main navigation stacks here
const Home = createStackNavigator({
HomeScreen,
Activity1,
Activity2,
Activity3,
});
//The following two are for the bottom tab bar only
const Calendar = createStackNavigator({ Calendar });
const Graph = createStackNavigator({ Graph });
const BottomTabNav = createBottomTabNavigator({
Home, Calendar, Graph
});
export default createAppContainer(TabNavigator);
And here's the code for DrawerMenu.js
import { Dimensions } from 'react-native';
import { createDrawerNavigator, createAppContainer } from 'react-navigation';
import Settings from './Settings';
import Profile from './Profile';
const SCREENWIDTH = Dimensions.get('window').width;
const DrawerConfig = {
drawerWidth: SCREENWIDTH * 0.5,
drawerPosition: 'right',
};
const DrawerMenu = createDrawerNavigator({
Settings: { screen: Settings },
Profile: { screen: Profile },
},
DrawerConfig
);
export default createAppContainer(DrawerMenu);
I couldn't get the drawer to work. When I click on the "Menu" button at the top right corner in the Home Screen to invoke toggleDrawer(), I got an "undefined is not an object (evaluating 'ae.props.navigation')" error.
The drawer cannot be activated using gesture either, so I think I am not adding it correctly. What did I do wrong here? Thanks!!

install the package the react-native-drawer
https://www.npmjs.com/package/react-native-drawer
<Drawer
type="overlay"
ref={ref => (this._drawer = ref)}
content={<Sidebar />}
tapToClose={true}
openDrawerOffset={0.2} // 20% gap on the right side of drawer
panCloseMask={0.2}
closedDrawerOffset={-3}
styles={drawerStyles}
tweenHandler={ratio => ({ main: { opacity: (2 - ratio) / 2 } })}
>
cds

Related

React Native drawer navigation as part of tab navigation

I'm trying to implement a drawer navigation alongside a tab navigation, but I can't seem to wrap my head around how that would be achieved.
The tab would have 5 items, and should be present on all screens. The 5th tab should open a drawer with more menu items.
Clicking on any of the drawer's menu items should of course show those specific screens, but the tab navigation should be still be present.
In the code below the bottom tab bar (which could be replaced by a top tab navigator) is always present.
The last tab contains the the drawer. It doesn't open by default, but this can be achieved by a this.props.navigation.openDrawer() request.
import React from 'react';
import { View, Text } from 'react-native';
import { createBottomTabNavigator, createDrawerNavigator } from 'react-navigation';
const buildScreen = name => {
class Screen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>{name}</Text>
</View>
);
}
}
return Screen;
}
const DrawerScreen = createDrawerNavigator(
{
Child1: buildScreen("DrawerChild 1"),
Child2: buildScreen("DrawerChild 2"),
},
{
// optional drawer options
// cfr. https://reactnavigation.org/docs/en/drawer-based-navigation.html
}
);
export default createBottomTabNavigator({
Tab1: buildScreen("Tab1"),
Tab2: buildScreen("Tab2"),
Tab3: buildScreen("Tab3"),
Tab4: buildScreen("Tab4"),
DrawerScreen,
});
I know this is an old question, but for people still looking for an answer, this is how I implemented mine.
The Drawer Navigation has to be your main navigation. Your tab navigation is nested inside it.
import React from 'react';
import { createDrawerNavigator } from '#react-navigation/drawer';
import AppNavigator from './AppNavigator';
const Drawer = createDrawerNavigator();
function DrawerNavigation() {
return (
<Drawer.Navigator>
<Drawer.Screen name='Home' component={TabNavigation} />
</Drawer.Navigator>
);
}
export default DrawerNavigation;
In your Tab Navigation, the tab which you choose to show your Drawer Navigation (in your case the fifth tab) would have a listener prop which fires a function on tabPress, toggling the drawer navigation
import React from 'react';
import { Text } from 'react-native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
const Home = () => (
<Text> Hello Home</Text>
);
const Menu = () => {
return null;
};
function TabNavigation(props) {
return (
<Tab.Navigator>
<Tab.Screen
name='Home'
component={Home}
/>
<Tab.Screen
name='Menu'
component={Menu}
listeners={({ navigation }) => ({
tabPress: e => {
e.preventDefault();
navigation.toggleDrawer();
},
})}
/>
</Tab.Navigator>
);
}
export default TabNavigation;
You can create a stackNavigator with two screens and a materialTabNavigator inside:
const SomeStack= createStackNavigator({
Start: StartStack,
HomeScreenTab,
}
and then:
HomeScreenTab: {
screen: HomeScreenTabTab
}
with:
export default createMaterialTopTabNavigator(
{
Home: {
screen: HomeStack,
navigationOptions: {
tabBarAccessibilityLabel: 'Tela Inicial do APP',
tabBarLabel: ({ tintColor }) => <LabelTitle tintColor={tintColor}
label="Start" />,
tabBarIcon: ({ tintColor }) => (
<View style={styles.containerIcon}>
<FontAwesome color={tintColor} name="home" size{icons.iconMd}
light />
</View>
),
},
},
...SomeOtherTabs
}
You put the content of your screens on the screen property and so on.

undefined is not an object (evaluating '_this.props.navigation.navigate') upon button click

I am fairly new to react-native and react-navigation and trying to move from one screen to another via button click, keep getting this error. I've found similar questions here on SO (this and this) but they don't work for me, keep getting this error.
What I am doing is, I have an App.js that is the initial screen which for now shows a login form (called Splash):
import React from 'react';
import {
View,
ActivityIndicator,
StyleSheet
} from 'react-native';
import { createDrawerNavigator, createAppContainer } from "react-navigation";
import HomeScreen from './HomeScreen';
import ProfileScreen from './ProfileScreen';
import Splash from './Splash';
export default class App extends React.Component {
render() {
const { navigation } = this.props;
return (
<View>
<Splash navigation={navigation}/>
</View>
);
}
}
const AppNavigator = createDrawerNavigator({
Home: {
screen: HomeScreen
},
Profile: {
screen: ProfileScreen
}
}, {
initialRouteName: "Home",
contentOptions: {
activeTintColor: '#e91e63'
}
});
const AppContainer = createAppContainer(AppNavigator);
On my Splash screen I now have no functionality as I am just trying to get it to move to my HomeScreen when I hit the Login button:
import React, { Component } from 'react';
import {
ScrollView,
Text,
TextInput,
TouchableOpacity,
StyleSheet
} from 'react-native';
class Splash extends Component {
constructor(props) {
super(props);
}
render() {
return (
<ScrollView style={{padding: 20}}>
<TextInput autoCapitalize="none"
onSubmitEditing={() => this.passwordInput.focus()}
autoCorrect={false}
keyboardType='email-address'
returnKeyType="next"
placeholder='Email address'
placeholderTextColor='rgba(225,225,225,0.7)'/>
<TextInput returnKeyType="go"
ref={(input)=> this.passwordInput = input}
placeholder='Password'
placeholderTextColor='rgba(225,225,225,0.7)'
secureTextEntry/>
<TouchableOpacity onPress={() => this.props.navigation.navigate('Home')}>
<Text>LOGIN</Text>
</TouchableOpacity>
</ScrollView>
)
}
};
export default Splash;
What happens now is that as soon as I hit the login button, I get the error in the title. Any clues on what I am doing wrong?
Remove App component, you don't need it in this case. Make Splash as your initial screen
const AppNavigator = createDrawerNavigator({
Splash: {
screen: Splash
},
Home: {
screen: HomeScreen
},
Profile: {
screen: ProfileScreen
}
}, {
initialRouteName: "Splash",
contentOptions: {
activeTintColor: '#e91e63'
}
});
then export AppContainer directly
const AppContainer = createAppContainer(AppNavigator);
export default AppContainer

React Navigation Drawer contentComponent communicate with screen

I using React Navigation Drawer with the custom content component.
Below is my code to create Drawer Navigation:
import { Dimensions } from 'react-native';
import { createDrawerNavigator, createAppContainer } from 'react-navigation';
import DrawerContent from '../components/drawer/DrawerContent'
import MainScreen from '../layout/MainScreen';
const MyDrawerNavigator = createDrawerNavigator({
MainDrawer: {
screen: MainScreen
},
},
{
initialRouteName: 'MainDrawer',
contentComponent: DrawerContent,
drawerWidth: Dimensions.get('window').width / 1.5,
drawerPosition: 'left',
gesturesEnabled: false,
}
);
export default createAppContainer(MyDrawerNavigator);
In the DrawerContent, I created the component and it has more item in there.
export default class DrawerContent extends Component {
onClickDrawerItems(type) {
if (type == "Logout") {
//I want to send a call back to MainScreen for showing a dialog
}
}
render() {
return (
<View style={Styles.drawerContainer}>
{/* Header Drawer */}
<ImageBackground
resizeMode='cover'
source={Assets.Image.im_logo_prasac}
style={Styles.headerDrawerImageBackground}>
<View style={Styles.headerBackgroundOverlay}>
............
</View>
</ImageBackground>
<ItemsDrawer
icon={Assets.Icon.ic_logout}
title={Strings.DrawerItems.logout}
onClick={() => this.onClickDrawerItems("Logout")} />
</View>
);
}
}
Inside my onClickDrawerItems function, how can I send a call back to MianScreen for showing a dialog for logout
Instead of using a dialog box or alert box you can use a simple toast.
Use react-native-toasty
and on logout add the following
onLogout() {
await AsyncStorage.clear();
RNToasty.Error({ title: 'You have been logged out.' })
//this.props.navigation.navigate('SplashScreen')
}
You can navigate to any screen you want after logout.
Hope this helps.
You're good to go!

How to implement both DrawerNavigator and StackNavigator

I am developing an app using react-native-navigation and I want to have a StackNavigator and a DrawerNavigator in the project. So, I have implemented them in the app.js but the apps crashes giving this error "The development server returned response error with code: 500".I have implemented them separetly and it works but I couldn't implement them together.Any suggestion?
this is my code for app.js
import React, {
Component
} from 'react';
import {
StyleSheet,
Text,
View
} from 'react-native';
import {
createStackNavigator,
DrawerNavigator,
DrawerItems
} from "react-navigation";
import {
Container,
Header,
Content,
Thumbnail,
Button,
Body
} from 'native-base';
import Profile from './components/Profile.js';
import Main from './components/Main.js';
import Login from './components/Login.js';
import Product from './components/Product.js';
export default class App extends Component {
render() {
return (
<Navapp />
);
}
}
const styles = StyleSheet.create({
// styles here
});
export const Drawer = DrawerNavigator({
Main: {
screen: Main
},
Profile: {
screen: Profile
},
}, {
initialRouteName: 'Main',
contentComponent: CustomDrawer,
drawerPosition: 'Left',
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
});
export const CustomDrawer = (props) => (
<Container>
<Header style = {
styles.headerStyle
}>
<Body style = {
styles.bodyStyle
}>
<Thumbnail style = {
{
height: 100,
width: 100
}
}
source = {
require('../image/logo.png')
}/>
</Body>
</Header>
<Content>
<DrawerItems { ...props} />
</Content >
</Container>
)
export const Navapp = createStackNavigator({
Login: {
screen: Login
},
Drawer: {
screen: Drawer
},
Product: {
screen: Product
},
});
I had a very similar setup for my app, this is how I went about handling it. First I created a stack navigator with the routes that I wanted logged in users to see, and I place that navigator inside a drawer navigator (you can put more than one in there if you want). Finally I created my top-level navigator, whose initial route points to the login page; upon logging in I navigate the user to the second route, which points to my drawer navigator.
In practice it looks like this:
// Main Screens for Drawer Navigator
export const MainStack = StackNavigator({
Dashboard: {
screen: Dashboard,
navigationOptions: {
title: 'Dashboard',
gesturesEnabled: false,
headerLeft: null
}
},
Notifications: {
screen: Notifications,
navigationOptions: {
title: 'Notifications'
}
}
}, { headerMode: 'screen' } );
// Drawer Navigator
export const Drawer = DrawerNavigator({
MainStack: {
screen: MainStack
}
});
// Main App Navigation
export const AppStack = StackNavigator({
Login: {
screen: Login,
navigationOptions: {
header: null,
gesturesEnabled: false
}
},
Drawer: {
screen: Drawer,
navigationOptions: {
header: null,
gesturesEnabled: false
}
}
}, { headerMode: 'none' } );
// In Your App.js
<AppStack />
Note that in the last stack navigator, the drawer screen has header set to null; this is because with nested stack navigators you can sometimes run into an issue where you'll have duplicate headers. This will hide the top-level navigator's header and let you show / customize the headers for your nested navigators.
Probably the way we can implement both Stack and Drawer Navigation has been made much simpler. Here you guys can refer to my code.
import * as React from "react";
import { createStackNavigator } from "#react-navigation/stack";
import { createDrawerNavigator } from "#react-navigation/drawer";
import MenuComponent from "../MenuComponent";
import DishDetailComponent from "../DishDetailComponent";
import HomeComponent from "../HomeComponent";
/**
* #author BadalSherpa
* #function HomeNavigation
**/
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const HomeNavigation = (props) => {
return (
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Home' component={HomeComponent} />
<Stack.Screen name='Menu' component={MenuComponent} />
<Stack.Screen name='Dish-Detail' component={DishDetailComponent} />
</Stack.Navigator>
);
};
const MenuNavigation = (props) => {
return (
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Menu' component={MenuComponent} />
</Stack.Navigator>
);
};
const DrawerNavigation = () => {
return (
<Drawer.Navigator>
<Drawer.Screen name='Home' component={HomeNavigation} /> //Here is where we are combining Stack Navigator to Drawer Navigator
<Drawer.Screen name='Menu' component={MenuNavigation} />
</Drawer.Navigator>
);
};
export default DrawerNavigation;
Then you can simple return this inside NavigationContainer which will have both Stack and Drawer Navigator working together.
<NavigationContainer>
<HomeNavigation />
</NavigationContainer>
This is how I use them
const HomeStackNavigator = StackNavigator(
{
Home: {
screen: HomeScreen,
},
Chat: {
screen: ChatScreen,
},
},
{
initialRouteName: 'Home',
headerMode: 'screen',
},
);
const MainDrawerNavigator = DrawerNavigator(
{
Home: {
screen: HomeStackNavigator,
},
},
{
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
contentComponent: SlideMenu,
navigationOptions: {
drawerLockMode: 'locked-closed',
},
},
);
same issue with me, Then i just change my emulator and update the version and it worked 100%.
This is a slightly different take where the drawer navigator controls a stack navigator. Each selection in the drawer will push a child stack element so there is a one-to-one match between the drawer and stack. This is more typical behavior in my experience. All but the home have a title with back navigation indicator.
import React from 'react';
import {SafeAreaView, StyleSheet} from 'react-native';
import {
CompositeNavigationProp,
NavigationContainer,
} from '#react-navigation/native';
import {
createStackNavigator,
StackNavigationProp,
} from '#react-navigation/stack';
import HomeScreen from './HomeScreen';
import SettingsScreen from './SettingsScreen';
import {
createDrawerNavigator,
DrawerContentComponentProps,
DrawerContentOptions,
DrawerContentScrollView,
DrawerItem,
DrawerNavigationProp,
} from '#react-navigation/drawer';
type NoProps = Record<string, object>;
export type ScreenNavigationProps = CompositeNavigationProp<
StackNavigationProp<NoProps>,
DrawerNavigationProp<NoProps>
>;
export type DrawerProps = DrawerContentComponentProps<DrawerContentOptions>;
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator(); // https://reactnavigation.org/docs/stack-navigator/
/**
* Render the content of the drawer. When an item is selected
* it closes the drawer and pushes an element on the stack nav.
* #param props
*/
function CustomDrawerContent(props: DrawerProps) {
const navigation = (props.navigation as unknown) as ScreenNavigationProps;
const openScreen = (name: string) => () => {
navigation.navigate('Home', {screen: name});
navigation.closeDrawer();
};
return (
<DrawerContentScrollView {...props}>
{/* <DrawerItemList {...props} /> */}
<DrawerItem label="Home" onPress={openScreen('Home')} />
<DrawerItem label="Settings" onPress={openScreen('Settings')} />
</DrawerContentScrollView>
);
}
/**
* Render the Home Stack Navigator.
*/
function HomeStack() {
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeScreen}
options={{headerShown: false}}
/>
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
);
}
/**
* Render the Home Drawer with a custom drawerContent.
*/
function HomeDrawer() {
return (
<Drawer.Navigator
initialRouteName="Home"
drawerContent={(props) => <CustomDrawerContent {...props} />}>
<Drawer.Screen name="Home" component={HomeStack} />
</Drawer.Navigator>
);
}
const App = () => {
return (
<SafeAreaView style={styles.app}>
<NavigationContainer>
<HomeDrawer />
</NavigationContainer>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
app: {flex: 1},
});
export default App;
The HomeScreen can provide a menu to open the drawer:
const navigation = useNavigation<ScreenNavigationProps>();
const openDrawer = () => {
navigation.openDrawer();
};

Toggling a drawer from header icon using react-navigation

I am new to react-native and react-navigation.
Required Screen: A home page with a header (Thumbnail + title) , two tabs and a drawer that toggles onPress of the thumbnail.
Please check my code:
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View, TouchableOpacity} from 'react-native';
import {Icon, Button} from 'native-base';
const util = require('util');
import { TabNavigator, StackNavigator, DrawerNavigator, NavigationActions } from 'react-navigation';
import FirstScreen from './tabs/FirstScreen';
import SecondScreen from './tabs/SecondScreen';
import Profile from './Profile';
import SearchScreen from './tabs/SearchScreen';
const DrawerScreens = DrawerNavigator({
Profile: {screen: Profile}
},{
drawerPosition: 'left',
drawerWidth: 150
});
// A drawer navigator
const TabScreens= TabNavigator({
First: {screen: FirstScreen},
Second: {screen: SecondScreen}
});
// Started with a tab navigator to register two tabs
const MenuButton =({navigation})=>{
return(
<View>
<TouchableOpacity onPress={()=>(navigation.navigate('DrawerToggle'))}>
<Icon name="person" size= {20}/>
</TouchableOpacity>
</View>)};
export const StackScreens= StackNavigator({
Draw: {screen: DrawerScreens},
// Drawer navigator as a screen
Display: {screen: TabScreens,
// Making the tabnavigator as a screen
navigationOptions: ({navigation})=>({
title: 'Welcome',
headerLeft: <MenuButton navigation={navigation} />
})
},
},{
initialRouteName: 'Display'
});
On pressing of the thumbnail the TabScreens navigates to the DrawerScreens (since that is how i coded). I want to make it toggle. Can someone please help me.
Here's some general ideas on how to structure your navigator properly.
What react-navigation version are you using?
Try changing your MenuButton helper method to this and see if it helps
if (navigation.state.index === 0) {
navigation.navigate('DrawerOpen')
} else {
navigation.navigate('DrawerClose')
}