skip to Main Content

Would anyone be able to give me help regarding React Navigation with Expo? The issue is with the drawer component where the ‘Hamburger’ icon isn’t opening or closing the component when a user presses the icon. However, it does open/close on a default swipe gesture from React Navigation. Please see below for my code:

Router:

import React from 'react';
import { NavigationContainer, DrawerActions } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { IconButton } from 'react-native-paper'

import i18n from '../i18n/i18n';

//Theme
import Theme from '../theme/theme'

//Components
import Menu from '../components/Menu'

//Import Interfaces
import { RootDrawerParamList } from '../utils/typescript/type.d';
import { IProps } from '../utils/typescript/props.d';

//Import Screens
import Screen1 from '../screens/Screen1';  
import Screen2 from '../screens/Screen2';
import SettingsScreen from '../screens/Settings';

const Drawer = createDrawerNavigator<RootDrawerParamList>();

export default class Router extends React.Component<IProps, any> {
  constructor(props: IProps) {
      super(props);
  }

  render() {
    return (
      <NavigationContainer>
        <Drawer.Navigator 
          initialRouteName='Screen1'
          drawerContent={(props: any) => <Menu {...props} />}
          screenOptions={({
            swipeEnabled: true
        })}>
          <Drawer.Screen 
            name="Screen1" 
            component={Screen1} 
            initialParams={{ i18n: i18n, Theme: Theme }}
            options={({route, navigation} : any) => ({
              headerRight: () => (<IconButton icon="cog" size={24} color={Theme.colors.text} onPress={() => navigation.navigate('Settings')} />),
              route: {route}, 
              navigation: {navigation}
            })}
          />
          <Drawer.Screen 
            name="Settings" 
            component={SettingsScreen} 
            initialParams={{ i18n: i18n, Theme: Theme }}
            options={({route, navigation} : any) => ({
              headerTitle: i18n.t('settings', 'Settings'),
              headerLeft: () => (<IconButton icon="arrow-left" color={Theme.colors.text} size={24} onPress={() => navigation.goBack()} />),
              route: {route}, 
              navigation: {navigation}
            })}
          />
          <Drawer.Screen 
            name="Screen2" 
            component={Screen2} 
            initialParams={{ i18n: i18n, Theme: Theme }}
            options={({route, navigation} : any) => ({
              route: {route}, 
              navigation: {navigation}
            })}
          />
        </Drawer.Navigator>
      </NavigationContainer>
    );
  }
}

Menu

import React from 'react';
import { FlatList, StyleSheet, View } from 'react-native';
import { List, Title } from 'react-native-paper';
import { getDefaultHeaderHeight } from '@react-navigation/elements';
import { DrawerItem } from '@react-navigation/drawer';
import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context';

//Import Interfaces
import { IListItem } from '../utils/typescript/types.d';
import { IPropsMenu } from '../utils/typescript/props.d';
import { IStateMenu } from '../utils/typescript/state.d';

//A function is used to pass the header height, using hooks. 
function withHeightHook(Component: any){
  return function WrappedComponent(props: IPropsMenu) {
    /*
      Returns the frame of the nearest provider. This can be used as an alternative to the Dimensions module.
    */
    const frame = useSafeAreaFrame();
    /*
      Returns the safe area insets of the nearest provider. This allows manipulating the inset values from JavaScript. Note that insets are not updated synchronously so it might cause a slight delay for example when rotating the screen.
    */
    const insets = useSafeAreaInsets();
    
    return <Component {...props} headerHeight={getDefaultHeaderHeight(frame, false, insets.top)} />
  }
}

class Menu extends React.Component<IPropsMenu, IStateMenu> {
  constructor(props: IPropsMenu) {
    super(props);

    this.state = {
      menu: [
        {
          name: 'screen1.name',
          fallbackName: 'Screen 1',
          icon: 'dice-multiple-outline',
          iconFocused: 'dice-multiple',
          onPress: this.props.navigation.navigate.bind(this, 'screen1')
        },
        { 
        name: 'screen2.name',
        fallbackName: 'Screen 2',
          icon: 'drama-masks',
          iconFocused: 'drama-masks',
          onPress: this.props.navigation.navigate.bind(this, 'screen2')
        }
      ]
    }
  }

  renderItem = (item : IListItem) => {
    const { i18n } = this.props.state.routes[0].params;

    return (
      <DrawerItem
        label={ i18n.t(item.name, item.fallbackName) }
        onPress={ item.onPress ? item.onPress: () => {} }
        icon={ ({ focused, color, size }) => <List.Icon color={color} style={[styles.icon, {width: size, height: size }]} icon={(focused ? item.iconFocused : item.icon) || ''} /> }
      />
    );
  };

  render() {
    const { headerHeight } = this.props;
    const { menu } = this.state;
    const { Theme } = this.props.state.routes[0].params;

    return (
      <View>
        <View style={{
          backgroundColor: Theme.colors.primary,
          height: headerHeight ?? 0,
        }}>
          <View style={{
            flex: 1,
            flexDirection: 'column',
            justifyContent: 'flex-end',
          }}>
            <View style={{ 
              flexDirection: 'row', 
              alignItems: 'center', 
              marginBottom: 5,
              marginLeft: 5
            }}>
              <Title style={{ color: Theme.colors.text, marginLeft: 5 }}>
                Title
              </Title>
            </View>
          </View>
        </View>
        <FlatList 
          data={menu}
          keyExtractor={item => item.name}
          renderItem={({item}) => this.renderItem(item)}
        />
      </View>
    );
  };
}

export default withHeightHook(Menu);


const styles = StyleSheet.create({
  icon: {
    alignSelf: 'center', 
    margin: 0, 
    padding: 0, 
    height:20
  },
  logo: {
    width: 24,
    height: 24,
    marginHorizontal: 8,
    alignSelf: 'center'
  },
});

2

Answers


  1. Chosen as BEST ANSWER

    The solution to my issue was to encapsulate the drawer component in a native stack component. The 'Hamburger' icon works as expected, thanks for all the help and suggestions.

    // Import Screens
    import DrawRouter from './DrawRouter';
    
    const Stack = createNativeStackNavigator<RootStackParamList>();
    
    export default (): React.ReactElement => {
      return (
        <NavigationContainer>
          <Stack.Navigator screenOptions={{ headerShown: false }}>
            <Stack.Screen name="Main" component={DrawRouter} />
          </Stack.Navigator>
        </NavigationContainer>
      );
    };
    

  2. Add the toogleDrawer() method to your onPress event on your Drawer.Navigator component:

    onPress={()=> navigation.toggleDrawer()
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search