24

I have an application using React native where I am using react-navigation (5.2.9).

I built a Stack.Navigator where I've got my screens but I want the Footer component to be outside so it renders in all screens. The problem is, I can't navigate from the footer, which is what I need to do as the footer has a few buttons that should be changing the screen:

const Stack = createStackNavigator();

const App = () => {    
  return (
    <Provider store={store}>
      <NavigationContainer>
        <Header />
        <Stack.Navigator>
          <Stack.Screen
            name="Home"
            component={HomeScreen}
            options={{
            headerShown: false
          }}
          />
          <Stack.Screen
            name="Login"
            component={LoginScreen}
            options={{
            headerShown: false
          }}
          />
        </Stack.Navigator>
        <Footer />
      </NavigationContainer>
    </Provider>
  );
};

How do I pass the navigation prop to the footer component?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Carlos Saiz Orteu
  • 1,735
  • 1
  • 10
  • 18

6 Answers6

63

Try this:

Create a new file named: RootNavigation.js

// RootNavigation.js

import * as React from 'react';

export const navigationRef = React.createRef();

export function navigate(name, params) {
  navigationRef.current?.navigate(name, params);
}
// file where you have the navigation

import {navigationRef} from './path/to/RootNavigation';

      <NavigationContainer ref={navigationRef}>
      .....
        <Footer />
      </NavigationContainer>

And Footer.js should be something like this:

// Footer.js

import React from 'react';
import {View, Button} from 'react-native';
import * as RootNavigation from './path/to/RootNavigation';

const Footer= () => {
  return (
    <View>
      <Button
        title={'Go to'}
        onPress={() =>
          RootNavigation.navigate('screenName ', {userName: 'Lucy'})
        }
      />
    </View>
  );
};

export default Footer;

For more info you can read the documentation. https://reactnavigation.org/docs/navigating-without-navigation-prop/

Soufiane Odf
  • 1,054
  • 2
  • 9
  • 23
22

The components outside of the Stack.Screen components do not receive the navigation prop. So in this case, you have to get the NavigationContainer using refs in your footer component, as follows:

  1. Create a ref with const myRef = React.createRef()
  2. pass it to the <NavigationContainer ref={myRef}>, and
  3. use that ref to navigate, myRef.current?.navigate(name, params).

It is all explained here. The docs here separate the creation of the ref in a new file, to allow you to import the ref without dependency loops/ issues. As the docs state, you can now call RootNavigation.navigate('ChatScreen', { userName: 'Lucy' }); in any js module, not just in a react component.

In your case though, you don't need the ref in separate file.

const Stack = createStackNavigator();
const navigationRef = React.createRef();

const App = () => {    
  return (
    <Provider store={store}>
      <NavigationContainer ref={navigationRef}>
        <Header />
        <Stack.Navigator>
          <Stack.Screen
            name="Home"
            component={HomeScreen}
            options={{
            headerShown: false
          }}
          />
          <Stack.Screen
            name="Login"
            component={LoginScreen}
            options={{
            headerShown: false
          }}
          />
        </Stack.Navigator>
        <Footer navigationRef={navigationRef}/>
      </NavigationContainer>
    </Provider>
  );
};

And in <Footer/> use navigationRef.current?.navigate(name, params)

Ben Butterworth
  • 22,056
  • 10
  • 114
  • 167
9

From the documentation. https://reactnavigation.org/docs/connecting-navigation-prop/

import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '@react-navigation/native';

function GoToButton({ screenName }) {
  const navigation = useNavigation();

  return (
    <Button
      title={`Go to ${screenName}`}
      onPress={() => navigation.navigate(screenName)}
    />
  );
}
3

A simple trick to extract navigation out of screenOptions.

function NavWrapper() {
  const navigatorRef = useRef<any>();
  <Tab.Navigator
    screenOptions={({ navigation: nav }) => navigatorRef.current = nav}
  >
    <Tab.Screen name="screen1" />
  </Tab.Navigator>
}
wang chenyu
  • 79
  • 1
  • 3
2

I ended up building a component called screen that will just wrap the content of screen and render the header/footer based on props:

import React from 'react';
import { View } from 'native-base';
import style from './style';
import Footer from '../../footer';
import Header from '../../header';

const Screen = ({
    footer,
    header,
    children,
    navigation
}) => (
  <View style={style.screen}>
    { header && <Header navigation={navigation} />}
    { children }
    { footer && <Footer navigation={navigation} />}
  </View>
);

export default Screen;

And wrapping the screens of my apps like this:

<Screen header footer navigation={navigation}>
    ... screen content
</Screen>

I feel like it is the best way if I can't sort that problem out.

Carlos Saiz Orteu
  • 1,735
  • 1
  • 10
  • 18
0

I solve this problem with a global state using React context API:

  // When enter in the HomeScreen:
  const { setGlobalNavigation, globalNavigation } = useGlobalContext();
  const navigation = useNavigation<RemindersNavigationProp>();

  useEffect(() => {
    if (setGlobalNavigation && !globalNavigation) setGlobalNavigation(navigation);
  }, [setGlobalNavigation, globalNavigation, navigation]);

  // When want to use:
  const { globalNavigation } = useGlobalContext();
  const handleNavigate = () => 
  globalNavigation.navigate("contactsStack", { screen: "newContact" });