react-navigation [v6] drawer and stack nesting issues - react-native

Like the most common use case, I want to have a drawer in my react-native application where, from all screens header I can open/close the drawer, navigate to them. Also, each screen has an option (button, card,...) that on press should navigate to any other screen.
So I have defined the following structure. But there are several problems;
Drawer cannot recognize which screen in on focus now.
Drawer type check does not give auto-suggestions on navigation prop (e.g. props.navigation.navigate(" /* no suggestion on the present screens in the stack*/"))
On Android I feel that the overall performance drops significantly
So is this a good structure? From the official documentation, I could not find any hint on how to implement it? stack nested in drawer or vise-versa?
export type MainStackParamList = {
HomeScreen: undefined;
OverViewScreen: undefined;
WorkOrdersScreen: {id?: number; description?: string; tabIndex?: number};
PropertiesScreen: undefined;
PropertyDetailScreen: {propertyUnit: PropertyUnit};
};
export type MainDrawerParamList = {MainStack: NavigatorScreenParams<MainStackParamList>};
export type AppNavigationCompositeProps = CompositeScreenProps<
DrawerScreenProps<MainDrawerParamList, 'MainStack'>,
StackScreenProps<MainStackParamList>
>;
//____The navigation part______
const MainStack = createStackNavigator<MainStackParamList>();
const Drawer = createDrawerNavigator<MainDrawerParamList>();
/* the composite type is the only way I found to have access to
drawer fucntions such as toggleDrawer in the stack screens*/
const MainStackScreens = (navigation: AppNavigationCompositeProps) => (
<MainStack.Navigator initialRouteName={'HomeScreen'} screenOptions={MainStackScreenOptions(navigation)}>
<MainStack.Screen name="HomeScreen" component={HomeScreen} />
<MainStack.Screen name="OverViewScreen" component={OverViewScreen} />
<MainStack.Screen name="WorkOrdersScreen" component={WorkOrdersScreen} />
<MainStack.Screen name="PropertiesScreen" component={PropertiesScreen} />
<MainStack.Screen name="PropertyDetailScreen" component={PropertyDetailScreen} />
</MainStack.Navigator>
);
const Navigation: React.FC<{}> = () => {
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="MainStack"
screenOptions={{headerShown: false}}
drawerContent={props => <CustomDrawerContent {...props} />}>
<Drawer.Screen name="MainStack" component={MainStackScreens} />
</Drawer.Navigator>
</NavigationContainer>
);
};
export default Navigation;
const MainStackScreenOptions = (navigation: AppNavigationCompositeProps): StackNavigationOptions => {
return {
headerStyle: {backgroundColor: '#00aade'},
headerTintColor: '#fca903',
headerTitleAlign: 'center',
headerTitleStyle: {fontWeight: 'bold', fontStyle: 'italic'},
headerBackTitle: 'GoBack',
headerLeft: () => <IconButton icon="menu" color="white" onPress={() => navigation.navigation.openDrawer()} />
};
};
//___the drawer content is like
const CustomDrawerContent: React.FC<DrawerContentComponentProps> = props => {
return (
<DrawerContentScrollView>
<Drawer.Item label="Home" onPress={() => props.navigation.navigate('HomeScreen')} icon="star" />
<Drawer.Item label="OverView" onPress={() => props.navigation.navigate('OverViewScreen')} icon="star" />
<Drawer.Item label="WorkOrders" onPress={() => props.navigation.navigate('WorkOrdersScreen')} icon="star" />
<Drawer.Item label="Properties" onPress={() => props.navigation.navigate('PropertiesScreen')} icon="star" />
</DrawerContentScrollView>
);
};

Related

Drawer navigation hearder icon opening a different drawer

I have this nested structure, where I have a drawer navigation in the home page and one of pages navigates to another drawer navigation, in order to have the back button I added headerRight and HeaderLeft. The headerRight is supposed to open the child navigator but instead it goes back to open the parent navigator. Any help
const SFrench = () => {
const navigation= useNavigation()
return (
<Drawer.Navigator
screenOptions={{
drawerStyle: {
backgroundColor: Colors.primary,
},
drawerActiveTintColor: Colors.secondary,
drawerInactiveTintColor: 'white',
headerLeft: () => {
return (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<CustomItem
iconName= {Platform==='android'? 'arrow-back' : "arrow-back-ios" }
onPress={() =>
navigation.pop()
}
/>
</HeaderButtons>
);
},
headerRight: () => {
return (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<CustomItem
iconName= "menu"
onPress={() =>
navigation.toggleDrawer()
}
/>
</HeaderButtons>
);
},
}}>
<Drawer.Screen key="1" name="page 1" component={SFPage1} />
<Drawer.Screen key="2" name="page 2" component={SFPage2} />
<Drawer.Screen key="3" name="page 3" component={SFPage3} />
</Drawer.Navigator>
);
};

React Native Drawer Navigation show headerLeft

I have a Drawer navigator which is inside a Stack navigator and I'd like to display a header. Currently I can display everything I want however because the header is defined at the Stack level the navigation inside the header is stack level not drawer level which is preventing me from opening the drawer.
Root stack
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ auth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
headerRight: () => (
<HeaderRight navigation={ navigation } />
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
Drawer stack
<Drawer.Navigator options={{
headerShown: true,
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
}}>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ChatListScreen}
/>
<Drawer.Screen
name="ChatRoom"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null
}}
component={ChatRoomScreen}
/>
</Drawer.Navigator>
Note in the drawer navigator the line with headerLeft does nothing and is there to show where I attempted to put it thinking it would work. I did think it might be overlaying the stack one so I commented out the stack one and it didn't work.
HeaderLeft
export default function HeaderLeft ({ navigation }) {
const openMenu = () => {
navigation.toggleDrawer();
}
return (
<View style={styles.header}>
<Icon name='menu' onPress={openMenu} size={28} style={styles.icon} color="white"/>
</View>
)
}
My question is how can I refactor this to enable me to have the HeaderLeft component work to open the drawer. I will be adding more screens so ideally something I don't have to pass to each screen but if that is what works I am good with it too.
Options in DrawerStack not work. I modified it:
<Drawer.Navigator
screenOptions={{
headerLeft: () => <HeaderLeft />,
}}>
// ...
</Drawer.Navigator>
And a little change in HeaderLeft:
import { useNavigation } from '#react-navigation/native';
function HeaderLeft() {
const navigation = useNavigation();
const openMenu = () => {
navigation.toggleDrawer();
};
// render your Button
}
Demo: https://snack.expo.dev/#pqv2210/0d613b

React-navigation default drawer icon, how to change it?

How can I change the icon point in the image below? I have not been able to find the documentation in the docs, I would like to change it to a different icon but I have no idea how to do this.
I found documentation about drawerIcon but as far as I know and manage to implement that icon is for the menu items in the drawer it self not for the screen header.
Here is my code:
const Drawer = createDrawerNavigator();
const headerOptions = {
title: 'Task List',
drawerIcon: ({ focused, size, color }) => <Ionicons name="ios-pizza" color="red" size={24} />,
};
const HomeScreen = ({ navigation }, props) => {
return (
<Drawer.Navigator screenOptions={{ drawerType: 'front' }}>
<Drawer.Screen name="TaskList" component={TaskListScreen} options={headerOptions} />
<Drawer.Screen name="TaskView" component={TaskViewScreen} />
<Drawer.Screen name="Notifications" component={Notifications} />
<Drawer.Screen name="Login" component={LoginScreen} />
</Drawer.Navigator>
);
};
But as mention before it renders the icons in the drawer item as shown below
headerLeft: Function which returns a React Element to display on the left side of the header. You can use it to implement your custom left button, for example:
<Drawer.Navigator
screenOptions={({ navigation }) => ({
headerLeft: props => <IconComponent onPress={navigation.toggleDrawer} />,
})}
>
...
</Drawer.Navigator>
<Drawer.Navigator
screenOptions={{
headerTintColor: 'red', // -----> by changing color from here
}} drawerContent={props => <DrawerContent {...props} />} >
<Drawer.Screen />
</Drawer.Navigator >
you can also use a image as a drawer icon.
screenOptions={({ navigation }) => ({
headerLeft: () =>
<Pressable onPress={navigation.toggleDrawer}>
<Text>
<Avatar.Image size={32} source={{ uri: deafaultImage }} />
</Text>
</Pressable >
})}

React Native Open Drawer with Button

I have a Drawer Navigator with some Screens. One of the Screens is a TabNavigator. Now I want to implement a HamburgerMenu-Button to open the drawer in all of the Tab-Navigator Screens.
Where do I implement that Button? In the TabNavigator or its child Screens or in the Drawer Screens?
Here are a few Code Snippets:
DrawerNavigator:
export default class DrawerMenu extends React.Component {
render() {
return (
<Drawer.Navigator initialRouteName='Home'>
<Drawer.Screen name='Home' component={TabNavigator} />
<Drawer.Screen name='About Us' component={AboutUsScreen} />
<Drawer.Screen name='About the App' component={AboutTheAppScreen} />
<Drawer.Screen name='Impressum' component={ImpressumScreen} />
</Drawer.Navigator>
);
}
}
TabNavigator:
const Tab = createBottomTabNavigator();
const TabNavigator = () => {
return (
<Tab.Navigator tabBar={(props) => <TabBar {...props}></TabBar>}>
<Tab.Screen
name='Tab1'
component={Tab1}
initialParams={{ icon: 'info' }}
></Tab.Screen>
<Tab.Screen
name='Tab2'
component={Tab2}
initialParams={{ icon: 'circle-o-notch' }}
></Tab.Screen>
<Tab.Screen
name='Tab3'
component={Tab3}
initialParams={{ icon: 'home' }}
></Tab.Screen>
<Tab.Screen
name='Tab4'
component={Tab4}
initialParams={{ icon: 'glass' }}
></Tab.Screen>
<Tab.Screen
name='Tab5'
component={Tab5}
initialParams={{ icon: 'road' }}
></Tab.Screen>
</Tab.Navigator>
);
};
single Screen in TabNavigator:
const Tab1 = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>Tab1</Text>
</View>
);
};
Any help is appreciated.
In the end it was an easy answer. We just had to give through the props. (I think)
single Screen in TabNavigator:
const Tab1 = ({navigation}) => {
return (
<View style={styles.container}>
<Text style={styles.text}>Tab1</Text>
</View>
);
};
That way we could easily use the navigation.openDrawer function in an onPress event.

How to call toggleDrawer in React Navigation 5

I got a React-Navigation 5 drawer menu working using gesture, but I also want to add an icon on the right side of the header to toggle the drawer menu.
I have the navigation setup in my App.js like this:
import {NavigationContainer, DrawerActions} from '#react-navigation/native';
//....... other imports
const HomeStack = createStackNavigator();
const HomeStackScreens = () => (
<HomeStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#5C6BC0',
},
headerTintColor: '#fff',
headerRight: () => {
return (
<TouchableOpacity
onPress={() => DrawerActions.toggleDrawer()}>
<Icon name="bars" color="#FFF" size={18} />
</TouchableOpacity>
);
},
}}>
<HomeStack.Screen
name="Home"
component={HomeScreen}
options={{
header:({scene, previous, navigation}) => {
return (
<TouchableOpacity onPress={() => navigation.toggleDrawer()}>
<Icon name="bars" color="#FFF" size={18} />
</TouchableOpacity>
);
}
}}
/>
<HomeStack.Screen name="Login" component={Login} />
<HomeStack.Screen name="Register" component={Register} />
</HomeStack.Navigator>
);
const ProfileStack = createStackNavigator();
const ProfileStackScreens = () => (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Profile" component={Profile} />
</ProfileStack.Navigator>
);
const SettingStack = createStackNavigator();
const SettingStackScreens = () => (
<SettingStack.Navigator>
<SettingStack.Screen name="Profile" component={Profile} />
</SettingStack.Navigator>
);
const Drawer = createDrawerNavigator();
const DrawerScreens = () => (
<Drawer.Navigator>
<Drawer.Screen name="Home" component={HomeStackScreens} />
<Drawer.Screen name="Profile" component={ProfileStackScreens} />
<Drawer.Screen name="Settings" component={SetttingStackScreens} />
</Drawer.Navigator>
);
class MyApp extends React.Component {
render() {
return (
<NavigationContainer>
<DrawerScreens />
</NavigationContainer>
);
}
}
export default MyApp;
All my others screen are in the form "export default class ScreenName extends React.Component". They are imported in App.js to setup the navigation. The initial screen is Home. The icon is showing correctly on the right side of the header. Calling "DrawerActions.toggleDrawer()" directly does nothing.
I tried "this.props.navigation.toggleDrawer()", and it throws error because "this.props" is undefined.
How can I invoke toggleDrawer() with such a navigation setup? Any help is really appreciated!
Here is the final solution I come up with that requires minimal changes to my original code. The key is to receive "navigation" in screenOptions or options, then I can call navigation methods in the children of screenOptions/options.
<HomeStack.Navigator
screenOptions={({navigation}) => ({
headerStyle: {
backgroundColor: '#5C6BC0',
},
headerTintColor: '#fff',
headerRight: () => {
return (
<TouchableOpacity
style={{paddingRight: 8}}
onPress={() => navigation.toggleDrawer()}>
<Icon name="bars" color="#FFF" size={18} />
</TouchableOpacity>
);
},
})}>