React-native - undefined is not a function (evaluating navigation.openDrawer()')

Viewed 8952

I'm very new to React native. I'm going to share my codes here App.js

import React, {Component} from 'react';
import Routes from './src/Routes';

export default class App extends Component {

  render() {
    return (
      <Routes />
    );
  }
}

index.js

import React, { Component } from 'react';
import { AppRegistry, Dimensions } from 'react-native';
import { createDrawerNavigator } from 'react-navigation';
import App from './App';
import {name as appName} from './app.json';
import SideMenu from './src/SideMenu'
import Routes from './src/Routes';

const drawernav = createDrawerNavigator({
  Item1: {
      screen: Routes,
    }
  }, {
    contentComponent: SideMenu,
    drawerWidth: Dimensions.get('window').width - 120,  
});

AppRegistry.registerComponent(appName, () => drawernav);
AppRegistry.registerComponent(appName, () => App);

Routes.js

import React, {Component} from 'react';
import { createStackNavigator, createAppContainer, createDrawerNavigator, DrawerActions } from 'react-navigation';
import Home from './Home';
import Settings from './Settings';
import SideMenu from './SideMenu';
import { Button, Platform, StyleSheet, Text, View, TouchableOpacity } from 'react-native';

const Nav = createStackNavigator({
  Home: {
    screen: Home,
    navigationOptions: ({navigation}) => ({
      title: "Home",
      headerLeft:(<TouchableOpacity onPress={() => navigation.openDrawer()}>
                <Text >Button</Text>
              </TouchableOpacity>
   ),
  })
 },
  Settings: {
screen: Settings,
navigationOptions: ({navigation}) => ({
  title: "Settings",
})     
  },
});
const Routes = createAppContainer(Nav);
export default Routes;

SideNav.js

import PropTypes from 'prop-types';
import React, {Component} from 'react';
import styles from './SideMenu.style';
import {NavigationActions} from 'react-navigation';
import {ScrollView, Text, View} from 'react-native';
import Home from './Home';
import Settings from './Settings';

class SideMenu extends Component {
  navigateToScreen = (route) => () => {
    const navigateAction = NavigationActions.navigate({
      routeName: route
    });
    this.props.navigation.dispatch(navigateAction);
  }

  render () {
    return (
      <View style={styles.container}>
        <ScrollView>
          <View>
            <Text style={styles.sectionHeadingStyle}>
              Section 1
            </Text>
            <View style={styles.navSectionStyle}>
              <Text style={styles.navItemStyle} onPress= 
   {this.navigateToScreen('Settings')}>
                  Page1
              </Text>
            </View>
          </View>
          <View>
            <Text style={styles.sectionHeadingStyle}>
              Section 2
            </Text>
            <View style={styles.navSectionStyle}>
              <Text style={styles.navItemStyle} onPress= 
   {this.navigateToScreen('Home')}>
                    Page2
              </Text>
              <Text style={styles.navItemStyle} onPress= 
   {this.navigateToScreen('Settings')}>
                    Page3
              </Text>
            </View>            
          </View>
          <View>
            <Text style={styles.sectionHeadingStyle}>
              Section 3
            </Text>
            <View style={styles.navSectionStyle}>
              <Text style={styles.navItemStyle} onPress= 
   {this.navigateToScreen('Settings')}>
                  Page4
              </Text>
            </View>
          </View>
        </ScrollView>
        <View style={styles.footerContainer}>
          <Text>This is my fixed footer</Text>
        </View>
      </View>
    );
  }
}

SideMenu.propTypes = {
  navigation: PropTypes.object
};

export default SideMenu;

Home.js

import React, { Component } from 'react';
import { View, Text, Button } from 'react-native';

export class Home extends Component {
  render() {
    return (
      <View >
        <Text>This is the home screen</Text>
        <Button onPress={() => this.props.navigation.navigate('Settings')} title="Settings"/>
      </View>
    )
  }
}

export default Home

When I click the button in the header, I get the error

undefined is not a function (evaluating navigation.openDrawer()')

I really dont mean to do this but I'm really desperate right now. I've spent two days trying to figure how to make a side menu and I'm really disappointed in this framework. You wont even spend 5 mins to create a side menu in Ionic. Kindly help on this. Project available on git https://github.com/yinka1255/react.git

3 Answers

The error

undefined is not a function (evaluating navigation.openDrawer()')

is being thrown because the navigation prop does not have an openDrawer method. Instead, you need to use the dispatch method and the DrawerActions helper:

import { DrawerActions } from 'react-navigation'
...
const Nav = createStackNavigator({
  Home: {
    screen: Home,
    navigationOptions: ({navigation}) => ({
      title: "Home",
      headerLeft:(
        <TouchableOpacity onPress={() => navigation.dispatch(DrawerActions.openDrawer())}>
          <Text>Button</Text>
        </TouchableOpacity>
      ),
    })
  },

DrawerActions has three methods that each return dispatchable actions. openDrawer, closeDrawer, and toggleDrawer which will toggle the Drawer state from open to closed and vice versa. Hope that helps!

undefined is not a function (evaluating navigation.openDrawer()')

DrawerActions from react-navigation is no longer available.

use import {DrawerActions} from '@react-navigation/native';

<TouchableOpacity
              onPress={() => navigation.dispatch(DrawerActions.openDrawer())}
              style={{
                width: 60,
                height: 40,
                padding: 10,
                paddingLeft: 20,
              }}>
              <Icon
                size={20}
                name="bars"
                style={{marginLeft: 2}}
                color="#fff"
              />
</TouchableOpacity>

It's not the framework, it's you.

You dont need to use app.js as a dummy component, you can directly use the same code of routes.js in your app.js . Wraping a navigator inside a component gives problems.

Second, you are using appregistry twice, you shoul use it once, it its going directyl to App.js wich doesn't have a drawer, and is not using drawernav at all, that's why undefined is not a function, there is no drawer .

index.js [the first file react-native looks into]

import { AppRegistry, Dimensions } from 'react-native';
import {name as appName} from './app.json';
import { createDrawerNavigator , createAppContainer } from 'react-navigation';
import SideMenu from './src/SideMenu'
import Routes from './src/Routes';

const drawernav = createDrawerNavigator({
  Item1: {
      screen: Routes,
    }
  }, {
    contentComponent: SideMenu,
    drawerWidth: Dimensions.get('window').width - 120,  
});

AppRegistry.registerComponent(appName, () => createAppContainer(drawernav));  //createAppContainer goes in your root navigator, in this case, a drawer

routes.js

import { createStackNavigator } from 'react-navigation';
import Home from './Home';
import Settings from './Settings';
import SideMenu from './SideMenu';
import { Button, Text, TouchableOpacity } from 'react-native';

const Nav = createStackNavigator({
  Home: {
    screen: Home,
    navigationOptions: ({navigation}) => ({
      title: "Home",
      headerLeft:(<TouchableOpacity onPress={() => navigation.openDrawer()}>
                <Text >Button</Text>
              </TouchableOpacity>
   ),
  })
 },
  Settings: {
    screen: Settings,
    navigationOptions: ({navigation}) => ({
    title: "Settings",
  })     
  },
}); 

export default Nav;

Also, Since you are just displaying one component in the drawer navigator (Routes), you should not be nesting the screens inside a stacknavigator, just pass it to drawernavigator and the drawernavigator will switch between them

const drawernav = createDrawerNavigator({
  Item1: {
      screen: Home,
      screen: Settings,
    }
  }, {
    contentComponent: SideMenu,
    drawerWidth: Dimensions.get('window').width - 120,  
});
Related