How to use navigation.navigate from a component outside the stack.navigation - react-native

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?

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/

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:
Create a ref with const myRef = React.createRef()
pass it to the <NavigationContainer ref={myRef}>, and
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)

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)}
/>
);
}

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.

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>
}

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" });

Related

React Navigation - Reset Screen on Tab Touch if screen is opened

I'm building a React Native app with React Navigation. I'm using Drawer Screens.
I need that if a Screen is opened, and I touch in its NavigatorButton, the screen will reset or at least recive an event.
NOTE: I'm not trying the screen reset when I touch to another, I need that if the screen is opened, reset it touching in his navigator tab
import React from "react";
import { NavigationContainer } from "#react-navigation/native";
import GameScreen from "./screens/GameScreen";
import { createDrawerNavigator } from "#react-navigation/drawer";
export default function App(params: { navigation: any }) {
const Drawer = createDrawerNavigator();
return (
<NavigationContainer>
<Drawer.Navigator useLegacyImplementation initialRouteName="Home">
<Drawer.Screen
name="Home"
options={{
title: "HomePage",
unmountOnBlur: true,
}}
component={GameScreen}
listeners={{
drawerItemPress: (e) => {},
}}
/>
<Drawer.Screen name="Nuevo juego" component={GameScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
SOLUTION
Well, I found a way to get "navigation" inside NavigationContainer. I hope it helps someone
import React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import GameScreen from './screens/GameScreen'
import {
createDrawerNavigator,
DrawerContent,
DrawerContentScrollView,
DrawerItem,
DrawerItemList,
} from '#react-navigation/drawer'
export default function App(params: { navigation: any }) {
const Drawer = createDrawerNavigator()
const CustomDrawers = (props: any) => {
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem
label={'Nuevo juego'}
onPress={() =>
props.navigation.reset({
index: 0,
routes: [{ name: 'Home' }],
})
}
/>
</DrawerContentScrollView>
)
}
return (
<NavigationContainer>
<Drawer.Navigator
useLegacyImplementation
initialRouteName="Home"
drawerContent={(props) => CustomDrawers(props)}>
<Drawer.Screen
name="Home"
options={{
title: 'Continuar',
// unmountOnBlur: true,
}}
component={GameScreen}
/>
</Drawer.Navigator>
</NavigationContainer>
)
}

How to hide the drawer header from stack's child component?

I would like to hide drawer navigation from the list component and it still be shown in the UserHomeScreen which is the stack navigation. It works if I just use the useState, hideHeader/setHideHeader, from App.js but it is not efficient way to do this. I tried my best to look for a solution but I really can't find. I am new to react native and have a limited knowledge.
Can anyone help me how am I going do it? Please help.
BTW here is the version of my drawer: ^6.0.1, if that helps.
Thank you so much!
App.js
import React from 'react'
// components
import UserHomeScreen from './src/components/user/pages/UserHomeScreen';
import Cart from './src/components/user/pages/Cart';
import Receipt from './src/components/user/pages/Receipt';
//redux
import store from './src/redux/store'
// libraries
import { NavigationContainer } from '#react-navigation/native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { Provider } from 'react-redux';
const Drawer = createDrawerNavigator();
const App = () => {
const [hideHeader, setHideHeader] = React.useState(false);
return (
<Provider store={store}>
<NavigationContainer>
{/* this hide drawer */}
{/* options={{headerShown: false}} */}
<Drawer.Navigator >
<Drawer.Screen name="Home" >
{() => <UserHomeScreen setHideHeader={setHideHeader} />}
</Drawer.Screen>
<Drawer.Screen name="Cart" component={Cart} />
<Drawer.Screen name="Receipt" component={Receipt} />
</Drawer.Navigator>
</NavigationContainer>
</Provider>
)
}
export default App;
UserHomeScreen.js
import React from 'react'
import List from '../components/User Home Screen/List';
import { createStackNavigator } from '#react-navigation/stack';
import ItemDetails from '../components/User Home Screen/ItemDetails';
import { useNavigation } from '#react-navigation/native';
import { Button } from 'react-native';
const Stack = createStackNavigator();
const UserHomeScreen = () => {
function backHandler(){
navigation.navigate('List')
}
// this hide the drawer header but I want it inside the list
// but if I do, the stack header that hides not the drawer header.
// React.useEffect(() => {
// navigation.setOptions({
// headerShown: false
// });
// })
const navigation = useNavigation();
return (
<Stack.Navigator>
<Stack.Screen name='List' component={List}>
</Stack.Screen>
<Stack.Screen name='Item Details' component={ItemDetails}
options={{
headerLeft: () => <Button title='Back' onPress={backHandler}/>
}}
/>
</Stack.Navigator>
)
}
export default UserHomeScreen;

How to current route name in react-navigation inside navigation.js file?

I want to know the current route name but inside the navigation js file, I use the useRoute hook in any component and work well, but I get this error when I use useRoute inside navigation.js
Error: Couldn't find a route object. Is your component inside a screen in a navigator?
navigation.js code example,
export default function Navigation() {
const route = useRoute(); // show error >> Error: Couldn't find a route object. Is your component inside a screen in a navigator?
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
</Stack.Navigator>
);
}
when I remove useRoute, the Error is gone, But I need to use useRoute or any other way to get current route name inside navigation.js file.
You can pass the navigationContainerRef from the NavigationContainer to the Navigation comomponent to make the navigation object accessible.
Consider the following code snippet.
import { createNavigationContainerRef } from "#react-navigation/native"
export const navigationRef = createNavigationContainerRef()
const App = () => {
return <NavigationContainer
ref={navigationRef}>
<Navigation navigation={navigationRef} />
</NavigationContainer>
}
export default App
Then, inside Navigation.
export default function Navigation({ navigation }) {
const route = navigation.current?.getCurrentRoute()
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
</Stack.Navigator>
);
}
The current route name get then be accessed using route?.name.
Edit: As jhon antoy correctly pointed out in the comments, this does not update the current route state if we navigate to a different screen. We need to update this ourselves as follows.
export const navigationRef = createNavigationContainerRef();
const App = () => {
const [routeName, setRouteName] = useState();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
setRouteName(navigationRef.getCurrentRoute().name)
}}
onStateChange={async () => {
const previousRouteName = routeName;
const currentRouteName = navigationRef.getCurrentRoute().name;
console.log("route", currentRouteName)
setRouteName(currentRouteName);
}}
>
<Navigation routeName={routeName} />
</NavigationContainer>
);
}
export default App;
Inside Navigation.
export function Navigation(props) {
const route = props.routeName
console.log(props)
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
</Stack.Navigator>
);
}
I have made a snack with some simple navigation buttons.
Works perfectly in v6.
In Your Stack, you have to convert the component to child.. in this way:
<RootStack.Screen
name={NameOfYourRoute}
children={props => (
<NameOfYourStack {...props} currentRoute={currentRoute} />
)}
/>

undefined is not an object react navigation from doc

I have this code that come directly from the doc of react navigation link here.
import * as React from 'react';
import { Button } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
const Stack = createNativeStackNavigator();
const MyStack = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
const HomeScreen = ({ navigation }) => {
return (
<Button
title="Go to Jane's profile"
onPress={() =>
navigation.navigate('Profile', { name: 'Jane' })
}
/>
);
};
const ProfileScreen = ({ navigation, route }) => {
return <Text>This is {route.params.name}'s profile</Text>;
};
const App = () => {
return (
<NavigationContainer>
<HomeScreen />
</NavigationContainer>
);
};
export default App;
and when i click on the button i have this error : undefined is not an object. can you help me found what's wrong ?
That just to test at the end i want different page in my app and navigate into,
i don't understant why all the different page is on the same file ...
Thank you,
have good coding day
The Reason Behind this is you are used HomeScreen component inside App component.You Have to use MyStack component inside the App component.
then you get error about NavigationContainer you have to use NavigationContainer one time not in nested.Please remove NavigationContainer from App component or MyStack component.
also Add ..
import{Text} from 'react-native';
congratulations Your Programm run perfectly.
I Changed small things in your code please copy and then try it will work.
import * as React from 'react';
import { Button,Text} from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
const Stack = createNativeStackNavigator();
const MyStack = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
const HomeScreen = ({ navigation }) => {
return (
<Button
title="Go to Jane's profile"
onPress={() =>
navigation.navigate('Profile', { name: 'Jane' })
}
/>
);
};
const ProfileScreen = ({ navigation, route }) => {
return <Text>This is {route.params.name}'s profile</Text>;
};
const App = () => {
return (
<MyStack />
);
};
export default App;
Happy Coding

Drawer Navigation react native paper

I am extremely new to React and React Native. I need some help regarding nesting a drawer navigator inside the current Stack Navigation.
I have the code on Snack, If someone can please help me i will really appreciate.
https://snack.expo.dev/#smith.james1982/github.com-callstack-react-native-paper-login-template
I want to put the Drawer navigation with Hamburger and Back Arrow using react-native-paper on the Home Screen.
Thanks very much in advance..
This is how i achieved the solution basically 2 navigations and using a state sharing library use-between. Hopefully it can be helpful to someone
import React, { memo, useState } from "react";
import { createStackNavigator } from "#react-navigation/stack";
import { createDrawerNavigator } from "#react-navigation/drawer";
import { NavigationContainer } from "#react-navigation/native";
import { HomeScreen, LoginScreen, RegisterScreen, ForgotPasswordScreen, Dashboard, Demo } from "./screens";
import { useLoginState } from "./core/state";
import { useBetween } from "use-between";
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
//Login navigation
const LoginNav = () => {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
initialRouteName="HomeScreen"
>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
<Stack.Screen name="LoginScreen" component={LoginScreen} />
<Stack.Screen name="RegisterScreen" component={RegisterScreen} />
<Stack.Screen name="ForgotPasswordScreen" component={ForgotPasswordScreen} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
);
};
//Logged in Navigation
const LoggedInNav = () => {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={Demo} />
</Drawer.Navigator>
</NavigationContainer>
);
};
export default function App() {
const { loggedIn, setIsLoggedIn } = useBetween(useLoginState);
return loggedIn ? <LoggedInNav /> : <LoginNav />;
}