react-nativereact-navigationreact-native-navigation

React Navigation - Opening modal from header button


I'm attempting to open a modal view from a header button. I'm using React Navigation v6 (https://reactnavigation.org/docs/modal/) as a guide.

The error I am getting an cannot seem to solve is: ERROR TypeError: Cannot read property 'navigate' of undefined, js engine: hermes

This is my code. Can anyone offer any help on how I can solve?

import Reactfrom 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Button, Modal, StyleSheet, Text, View} from 'react-native';

// Import Tab Screens
import ScreenA from './src/pages/screenA'
import ScreenB from './src/pages/screenB'
import ScreenC from './src/pages/screenC'

const Tab = createBottomTabNavigator();

function ScreenModal({ navigation }) {
  return (
    <View>
      <Text>This is a modal!</Text>
      <Button onPress={() => navigation.goBack()} title="Dismiss" />
    </View>
  );
}

export default function App({ navigation }) {

  return (
    <NavigationContainer>
      <Tab.Navigator>
      <Tab.Group>
        <Tab.Screen name="ScreenA" component={ScreenA} />
      </Tab.Group>
      <Tab.Group>
        <Tab.Screen name="ScreenB" component={ScreenB} 
            options={{
              headerRight: () => (
                <Button
                  onPress={() => navigation.navigate('ScreenModal')}
                  title="Open Modal"
                />
              ),
            }}
          />
        </Tab.Group>
      <Tab.Group>
        <Tab.Screen name="ScreenC" component={ScreenC} />
      </Tab.Group>
      </Tab.Navigator>
    </NavigationContainer>
  );
}

Solution

  • Because "ScreenModal" isn't a screen inside the "NavigationContainer" it won't receive the "navigation" prop. You could use the "useNavigation" hook to access it.

    const navigation = useNavigation()
    

    But that won't work anyway because ScreenModal isn't a screen so it can't be navigated to anyway, unless you have it defined somewhere else.

    Also react native has a Modal component which you might want to use. You can implement it like this:

    import { Modal } from 'react-native'
    
    const Tab = createBottomTabNavigator();
    
    export default function App() {
      const [showModal, setShowModal] = React.useState()
    
      return (
        <NavigationContainer>
          <Modal visible={showModal}>
            <View>
              <Text>This is a modal!</Text>
              <Button onPress={() => setShowModal(false)} title="Dismiss" />
            </View>
          </Modal>
    
    
          <Tab.Navigator>
          <Tab.Group>
            <Tab.Screen name="ScreenA" component={ScreenA} />
          </Tab.Group>
          <Tab.Group>
            <Tab.Screen name="ScreenB" component={ScreenB} 
                options={{
                  headerRight: () => (
                    <Button
                      onPress={() => setShowModal(true)}
                      title="Open Modal"
                    />
                  ),
                }}
              />
            </Tab.Group>
          <Tab.Group>
            <Tab.Screen name="ScreenC" component={ScreenC} />
          </Tab.Group>
          </Tab.Navigator>
        </NavigationContainer>
      );
    }
    

    Since Modals will render on top of anything you can add the component wherever you want, it doesn't need to be in the root component. But if you want to have the ScreenModal be a screen, then you need to have it as a screen in a stack navigator.

    const Tab = createBottomTabNavigator();
    const Stack = createStackNavigator(); //install and import this
    
    function ScreenModal({ navigation }) {
      return (
        <View>
          <Text>This is a modal!</Text>
          <Button onPress={() => navigation.goBack()} title="Dismiss" />
        </View>
      );
    }
    
    const HomeNavigator = ({ navigation }) =>{
      return(
        <Tab.Navigator>
          <Tab.Group>
            <Tab.Screen name="ScreenA" component={ScreenA} />
          </Tab.Group>
          <Tab.Group>
            <Tab.Screen name="ScreenB" component={ScreenB}
              options={{
                headerRight: () => (
                  <Button
                    onPress={() => navigation.navigate('ScreenModal')}
                    title="Open Modal"
                  />
                ),
              }}
            />
          </Tab.Group>
          <Tab.Group>
            <Tab.Screen name="ScreenC" component={ScreenC} />
          </Tab.Group>
        </Tab.Navigator>
      )
    }
    
    export default function App() {
    
      return (
        <NavigationContainer>
          <Stack.Navigator>
            <Stack.Screen name="Home" component={HomeNavigator}/>
            <Stack.Screen name="ScreenModal" component={ScreenModal} options={{presentation:'modal'}}/>
          </Stack.Navigator>
        </NavigationContainer>
      );
    }
    

    Hope this helps