17

I am trying to hide the ability to press one of my routes in the drawer navigator as it is another navigator and the default location in the app. I want the drawer to simply be used for navigating to extraneous routes that don't fit well into user flow elsewhere. Before React Navigation 5 I was able to achieve this by simply setting drawerLabel: () => null. However now with the changes I cannot figure out how to hide this in the same manner.

Below is my current navigator code:

const DrawerNavigator = () => {
    const dispatch = useDispatch();
    return (
    <MainDrawerNavigator.Navigator 
        drawerContent={props => customDrawerContent(props, dispatch)}
        drawerStyle={drawerStyle}
    >
        <MainDrawerNavigator.Screen 
            name="DrawerNav"
            component={MainTabNavigator}
            options={{
                drawerLabel: () => null,
                title: null,
                drawerIcon: () => null
            }}
        />

        <MainDrawerNavigator.Screen
            name="FAQNav"
            component={FAQStackNavigator}
            options={
                { 
                    drawerLabel: "FAQ", 
                    drawerIcon: ({tintColor}) => <EvilIcons name={'question'} size={30} color={tintColor} />
                }
            }
        />
    </MainDrawerNavigator.Navigator>
    )
}

const customDrawerContent = (props, dispatch) => {
    console.log(props.descriptors)
    return (
        <View style={{flex: 1}}>
            <View style={{height: '90%'}}>

                <DrawerContentScrollView {...props}>
                        <View style={styles.logoContainer}>
                            <Image 
                                style={styles.image} 
                                fadeDuration={0} 
                                resizeMode={'contain'} 
                                source={require('../assets/images/spikeball-logo-horizontal.png')} 
                            />
                        </View>

                        <TouchableOpacity style={styles.contactUsContainer} onPress={() => { Linking.openURL('https://spikeball.com/')}}>
                            <AntDesign style={styles.iconStyle} name={'shoppingcart'} size={25} color={'black'} />

                            <Text style={styles.drawerText}>Shop</Text>                    
                        </TouchableOpacity>

                        <TouchableOpacity style={styles.contactUsContainer} onPress={() => { Linking.openURL('https://support.spikeball.com/')}}>
                            <AntDesign style={styles.iconStyle} name={'contacts'} size={25} color={'black'} />

                            <Text style={styles.drawerText}>Contact Us</Text>                    
                        </TouchableOpacity>

                        <DrawerItemList 
                            {...props}
                        />

                </DrawerContentScrollView>
            </View>

            <TouchableOpacity 
                style={styles.logoutContainer} 
                onPress={() => { 
                    dispatch(authActions.logout());
                }}>
                    <Text style={styles.logoutText}>SIGN OUT</Text>                    
            </TouchableOpacity>
        </View>
    )
}

Link to image showing the undesired output. Basically I want the blue focus and entire nav item hidden from the naw bar specifically. UNDESIRED Output

SKeney
  • 1,965
  • 2
  • 8
  • 30

11 Answers11

32

Having looked through the React Navigation source code, I think the best option would be to add a drawerItemStyle option to the drawer screen that you want to hide. Just set the height to 0.

E.g.

<Drawer.Screen
  name="Home"
  component={MainStackScreen}
  options={{
    drawerItemStyle: { height: 0 }
  }}
/>

It seems to work perfectly.

I'm using React Navigation 6 but this will probably apply to v5 also.

SteveGreenley
  • 630
  • 1
  • 5
  • 7
27

Best solution will be filter the props before pass it to DrawerItemList . This will only work react navigation 5

//custom drawer content
export default props => {
const { state, ...rest } = props;
const newState = { ...state}  //copy from state before applying any filter. do not change original state
newState.routes = newState.routes.filter(item => item.name !== 'Login') //replace "Login' with your route name

return (
     <DrawerContentScrollView {...props}>
         <DrawerItemList state={newState} {...rest} />
    </DrawerContentScrollView>
)

}

Lenin Sheikh
  • 291
  • 4
  • 4
  • How to hide multiple drawer items? – MayankBudhiraja Aug 17 '20 at 02:08
  • 3
    hey @MayankBudhiraja update the Filter like this: newState.routes = newState.routes.filter(item => !['RouteOne', 'Login', 'Signup' ].includes(item.name)); – Lenin Sheikh Aug 18 '20 at 08:12
  • Not working in my case. Did exactly the same thing. Asking another question. – targhs Sep 27 '20 at 08:11
  • This is working. But the first item in the drawer had a blue background. Had to pass activeBackgroundColor={'transparent'} in DrawerItemList. Thanks. – Gowtham Nov 05 '20 at 15:15
  • 1
    Working as expected! – ffritz Nov 27 '20 at 10:34
  • 2
    working but sometimes it is not navigate to another screen – Nisharg Shah Dec 29 '20 at 15:32
  • Same as @Nisharg Shah, It does not navigate to the 2nd screen on the list. – Raphael Pinel Jan 05 '21 at 13:43
  • For the active drawer index to be correct, and the page to redirect correctly, you need to change those aswel in the newState object. In my case I had to change the screens depending on a user selected value "isBusiness", which was the 3rd item in the array of screens. Code: newState.routeNames = newState.routeNames.filter((item) => isBusiness ? item !== "Login" : item ); newState.index = newState.index > 2 && isBusiness ? newState.index - 1 : newState.index; // Index minus 1 for every screen coming after the original position of "Login" – Ruben van der Veen Jul 25 '23 at 07:55
25

This works Perfectly

<Drawer.Screen name="Home" component={MainStackScreen}
        options={{
                  drawerItemStyle: { display: 'none' }
        }}
/>
Abid Ali
  • 679
  • 7
  • 10
  • the code works on v6 too! – Lucas Garcez May 07 '22 at 22:18
  • this answer deserves more upvotes, display: none removes the item completely lincluding any padding or margin that was asociated with it. After going through all the solutions thinking there would just be some prop, This was a simpliest option that catches all scenarios – danielRicado Aug 15 '23 at 23:27
13

Solved the issue with the following

import React from 'react';
import { SafeAreaView, View, Text, StyleSheet, Image, Linking } from 'react-native';
import { EvilIcons, AntDesign } from '@expo/vector-icons';
import { useDispatch } from 'react-redux';
import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList, DrawerItem } from '@react-navigation/drawer';

import MainTabNavigator from './MainTabNavigator';
import FAQStackNavigator from './FAQStackNavigator';
import { TouchableOpacity } from 'react-native-gesture-handler';
import * as authActions from '../store/actions/auth';
import { moderateScale } from '../utils/fontScale';

const MainDrawerNavigator = createDrawerNavigator();

const DrawerNavigator = () => {
    const dispatch = useDispatch();
    return (
    <MainDrawerNavigator.Navigator 
        drawerContent={props => customDrawerContent(props, dispatch)}
        drawerStyle={drawerStyle}
    >
        <MainDrawerNavigator.Screen 
            name="DrawerNav"
            component={MainTabNavigator}
            options={{
                drawerLabel: () => null,
                title: null,
                drawerIcon: () => null
            }}
        />

        <MainDrawerNavigator.Screen
            name="FAQNav"
            component={FAQStackNavigator}
            options={
                { 
                    drawerLabel: "FAQ", 
                    drawerIcon: ({tintColor}) => <EvilIcons name={'question'} size={30} color={tintColor} />
                }
            }
        />
    </MainDrawerNavigator.Navigator>
    )
}

const customDrawerContent = (props, dispatch) => {
    return (
        <View style={{flex: 1}}>
            <View style={{height: '90%'}}>

                <DrawerContentScrollView {...props}>
                        <View style={styles.logoContainer}>
                            <Image 
                                style={styles.image} 
                                fadeDuration={0} 
                                resizeMode={'contain'} 
                                source={require('...')} 
                            />
                        </View>

                        <TouchableOpacity style={styles.contactUsContainer} onPress={() => { Linking.openURL('...')}}>
                            <AntDesign style={styles.iconStyle} name={'shoppingcart'} size={25} color={'black'} />

                            <Text style={styles.drawerText}>Shop</Text>                    
                        </TouchableOpacity>

                        <TouchableOpacity style={styles.contactUsContainer} onPress={() => { Linking.openURL('...')}}>
                            <AntDesign style={styles.iconStyle} name={'contacts'} size={25} color={'black'} />

                            <Text style={styles.drawerText}>Contact Us</Text>                    
                        </TouchableOpacity>

                        {/* Tried just disabling using DrawerItemList but wasn't working so made
                        complete custom drawer component and navigate properly using props.navigation.navigate */}
                        {/* <DrawerItemList 
                            {...props}
                        /> */}

                        <TouchableOpacity 
                            style={styles.contactUsContainer} 
                            onPress={() => { console.log(props.navigation.navigate('FAQNav'))}}
                        >
                            <EvilIcons name={'question'} size={30} color={'black'} />
                            <Text style={styles.drawerText}>FAQ</Text>                    
                        </TouchableOpacity>
                </DrawerContentScrollView>
            </View>

            <TouchableOpacity 
                style={styles.logoutContainer} 
                onPress={() => { 
                    dispatch(authActions.logout());
                }}>
                    <Text style={styles.logoutText}>SIGN OUT</Text>                    
            </TouchableOpacity>
        </View>
    )
}

const drawerStyle = {
    activeTintColor: 'black',
    inactiveTintColor: 'black',
    labelStyle: {
        fontFamily: 'montserrat',
        marginVertical: 16,
        marginHorizontal: 0,
    },
    iconContainerStyle: {
        justifyContent: 'center',
        alignItems: 'center',
    },
    itemStyle: {

    }
}

const styles = StyleSheet.create({
    safeArea: {
        flex: 1,
        paddingTop: Platform.OS === 'android' ? 25 : 0
    },
    container: {
        flex: 1,  
    },
    logoContainer: {
        width: '100%',
        height: moderateScale(50),
        alignItems: 'center',
        justifyContent: 'center',
        marginBottom: 5,
        padding: 5,
    },
    image: {
        resizeMode: 'contain',
        width: '80%',
        height: '100%',
    },
    contactUsContainer: {
        flexDirection: 'row',
        width: '100%',
        height: 50,
        alignItems: 'center',
        paddingLeft: 15
    },
    logoutContainer: {
        flexDirection: 'row',
        width: '100%',
        height: 50,
        alignItems: 'flex-end',
        justifyContent: 'center',
    },
    drawerText: {
        fontFamily: 'montserrat',
        marginLeft: 16,
    },
    logoutText: {
        fontFamily: 'montserrat',
        color: '#b23b3b'
    }
});

export default DrawerNavigator;
Jkkarr
  • 382
  • 4
  • 18
SKeney
  • 1,965
  • 2
  • 8
  • 30
  • @Matheus In my application they are not clickable at all. There is no clickable space. If I inspect the actual elements in the drawer they are not viewable or clickable. – SKeney May 27 '20 at 02:17
  • 3
    The drawer item is hidden but I can still some space at that position. Do you know how that can be removed? – harshit raghav Jun 04 '20 at 06:04
  • 1
    @harshitraghav I accomplished it by creating my own drawer content above `customDrawerContent`. If you notice `DrawerItemList` is commented out and then I handle navigation myself with my own item components. – SKeney Jun 05 '20 at 16:42
  • 1
    I have to apologize. I didn't check the part where you draw every item, it really worked. Change some letter in your answer so I can change my vote. Thanks for this answer. – Matheus Jun 20 '20 at 02:07
  • @Matheus No worries! Glad it helped! – SKeney Jun 20 '20 at 04:35
  • 1
    For it to work, you need to replace with the items you want to have in the list: ` props.navigation.navigate('yourNavItem')} labelStyle={styles.mainItem} />` – Raphael Pinel Jan 05 '21 at 14:27
  • Someone please help me I have same problem But I do not know what should I modify In code. Check this link = https://stackoverflow.com/questions/65679208/how-to-disabled-or-remove-or-hide-item-selected-space-in-react-native-drawer –  Jan 12 '21 at 07:36
3

For me you would better creating a nested navigator with stack and drawer screens as documented in https://reactnavigation.org/docs/nesting-navigators/#navigator-specific-methods-are-available-in-the-navigators-nested-inside instead of hiding drawer item.

Engin Yilmaz
  • 393
  • 1
  • 5
  • 17
  • This is also a not good solution, I've tried this. when doing that all you do is replace a bunch of screens you want to hide in drawer for "one" screen which is the stack navigator, for instance. Maybe you could provide a better example other than linking the docs, which are kida poor. Thanks – Matheus May 21 '20 at 21:17
  • To add to what @Matheus wrote, when I tried this way I was seeing some weird flickering on iOS and when tested it with the drawer as the top navigator its was pretty smooth. – rafaelmorais Dec 15 '20 at 17:04
3

One of the effective ways I find on how to do this is by setting the drawerLableStyle display to none. For example:


options: {
   drawerLabel: "Contact Us",
   drawerIcon: ({ color }) => <Ionicons name="ios-call-outline" size={22} color={color} />,
   drawerItemStyle: { display: "none" }
    
}

ObiTech Invents
  • 408
  • 3
  • 6
2

Solution for React Navigation 5

I ended up using the following code -

drawerContent={props => customDrawerContent(props, dispatch)}

Code of customDrawerContent -

{state.routes.map((route, i) => {
          if(route.name === 'App') return;
          const focused = i === state.index;
          const { title, drawerLabel, drawerIcon } = descriptors[route.key].options;

          return (
            <DrawerItem
              key={route.key}
              label={
                drawerLabel !== undefined
                  ? drawerLabel
                  : title !== undefined
                  ? title
                  : route.name
              }
              icon={drawerIcon}
              focused={focused}
              activeTintColor={activeTintColor}
              inactiveTintColor={inactiveTintColor}
              activeBackgroundColor={activeBackgroundColor}
              inactiveBackgroundColor={inactiveBackgroundColor}
              labelStyle={labelStyle}
              style={itemStyle}
              to={buildLink(route.name, route.params)}
              onPress={() => {
                navigation.dispatch({
                  ...(focused
                    ? DrawerActions.closeDrawer()
                    : CommonActions.navigate(route.name)),
                  target: state.key,
                });
              }}
            />
          );
        })}

Complete code can be found here -

https://gist.github.com/yashkumarsharma/a56f4fe1517ee8ce07c153d2d2795f6f

Yash
  • 929
  • 9
  • 25
2

None of the above solutions has worked for me until I have stepped upon How to hide DrawerView.Item in DrawerNavigator contentComponent #795 on Github.

Other folks comments allowed me to come up with a dynamic and working solution for react-native v5.

const CustomDrawer = props => {
  const {state, descriptors, navigation} = props;
  const buildLink = useLinkBuilder();

  return (
    <DrawerContentScrollView {...props}>
      {state.routes.map((route, i) => {
        const isHidden = descriptors[route.key].options?.hidden; // <--- Added this line
        if (isHidden === true) return null; // <--- Added this line

        const focused = i === state.index;
        const {
          title,
          drawerLabel,
          drawerIcon,
          drawerActiveTintColor,
          drawerInactiveTintColor,
          drawerActiveBackgroundColor,
          drawerInactiveBackgroundColor,
          drawerLabelStyle,
          drawerItemStyle,
        } = descriptors[route.key].options;

        return (
          <DrawerItem
            key={route.key}
            label={
              drawerLabel !== undefined
                ? drawerLabel
                : title !== undefined
                ? title
                : route.name
            }
            icon={drawerIcon}
            focused={focused}
            activeTintColor={drawerActiveTintColor}
            inactiveTintColor={drawerInactiveTintColor}
            activeBackgroundColor={drawerActiveBackgroundColor}
            inactiveBackgroundColor={drawerInactiveBackgroundColor}
            labelStyle={drawerLabelStyle}
            style={drawerItemStyle}
            to={buildLink(route.name, route.params)}
            onPress={() => {
              navigation.dispatch({
                ...(focused
                  ? DrawerActions.closeDrawer()
                  : CommonActions.navigate(route.name)),
                target: state.key,
              });
            }}
          />
        );
      })}
    </DrawerContentScrollView>
  );
};

export default CustomDrawer;

Then all you have to do is use CustomDrawer in Drawer.Navigator and pass hidden option to Drawer.Screen like so:

<Drawer.Navigator
              initialRouteName={ROUTES.SIGN_IN}
              drawerPosition="right"
              drawerContent={props => <CustomDrawer {...props} />}>
              <Drawer.Screen
                name={ROUTES.SIGN_IN}
                component={SignInContainer}
                options={{headerShown: false, hidden: true}}
              />
              <Drawer.Screen
                name={ROUTES.DASHBOARD}
                component={withHeader(DashboardContainer)}
                options={{headerLeft: false}}
              />
</Drawer.Navigator>
an0o0nym
  • 1,456
  • 16
  • 33
1

@Lenin Sheikh solution works but as highlighted in the comments, it introduces another issue where some components in the drawer menu cannot be clicked. I solved this by using two steps (obviously after applying @Lenin Sheikh solution).

  1. Move the hidden items toward the bottom of the list

  2. Choose a default component, especially if its one of the hidden components

    <Drawer.Navigator
              initialRouteName='Home'// Specify the default component
           drawerContent={
             (props) => (
               <DrawerList {...props} />)
           }>
    
           <Drawer.Screen name="Component 1"
                          component={Component1}
                          options={{ title: "Component one", headerShown: 
       false }} />
    
     <Drawer.Screen name="Component 2"
                      component={Component2}
                      options={{ title: "Component 2", headerShown: false 
    }} />
    
    
    
     <Drawer.Screen name="Logout"
                  component={LogoutPage}
                  options={{ title: "Logout", headerShown: true }} />
    
    <Drawer.Screen name="Home" component={HomePage}
                  options={{
                    title: "",
                    headerShown: false,
    
                  }} />//This is the hidden component
    
    </Drawer.Navigator>
    
Sean
  • 360
  • 3
  • 17
1

I've just found a solution to my similar problem. Long story short, I use Stack, Tabs and Drawer (for hamburger menu). I managed to combine all navigations, but drawer was showing Home button which I did not want.

The solution I found is here:

I have also encountered problem, that the 1st drawer menu option was highlighted and not clickable. For that I just had to move my hidden menu screen to the bottom of the drawer navigation. :) I know it is not the most beautiful solution, but I think I got it. I hope it will help someone. Here is my code:

function DrawerNavigator() {
  return (
    <NavigationContainer>
      <Drawer.Navigator
        initialRouteName="Home"
        drawerContent={props => {
          const filteredProps = {
            ...props,
            state: {
              ...props.state,
              routeNames: props.state.routeNames.filter(routeName => {
                routeName !== 'Home';
              }),
              routes: props.state.routes.filter(route => route.name !== 'Home'),
            },
          };
          return (
            <DrawerContentScrollView {...filteredProps}>
              <DrawerItemList {...filteredProps} />
            </DrawerContentScrollView>
          );
        }}>
        <Drawer.Screen name="Notifications" component={ProfileScreen} />
        <Drawer.Screen name="Notifications2" component={ProfileScreen} />
        <Drawer.Screen name="Home" component={StackNavigator} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

p.s. all screens are fake, so ignore that it makes no sense :)

I can and will share my whole navigation when I will get a good sleep and refactor it, but do not hesitate to ask if you need more code. Cheers!

wecoon
  • 155
  • 1
  • 11
1

Following up on @Lenin Sheikh, for the active drawer index to be correct, and the page to redirect correctly, you need to change those aswel in the newState object.

In my case I had to change the screens depending on a user selected value "isBusiness", which was the 3rd item in the array of screens.

//custom drawer content
export default props => {
const { state, isBusiness, ...rest } = props;
const newState = { ...state}  //copy from state before applying any filter. do not change original state
newState.routes = newState.routes.filter(item => isBusiness ? item.name !== 'Login' : item) // filter "Login' if isBusiness is true
newState.routeNames = newState.routeNames.filter((item) =>
    isBusiness ? item !== "Login" : item
  ); // filter "Login' if isBusiness is true
  newState.index =
    newState.index > 2 && isBusiness ? newState.index - 1 : newState.index; // Index minus 1 for every screen coming after the original position of "Login" which in my case is index 2.
return (
     <DrawerContentScrollView {...props}>
         <DrawerItemList state={newState} {...rest} />
    </DrawerContentScrollView>
)