switch navigator in react-navigation v5 - react-native

There is a good example of switch navigator in V4 documentation of react-navigation:
https://snack.expo.io/#react-navigation/auth-flow-v3
I didn't understand how can I change this into a proper way for V5. here is the link:
https://reactnavigation.org/docs/en/upgrading-from-4.x.html#switch-navigator
Any assistance you can provide would be greatly appreciated.

Well, if you want to implement a "switch" like feature with V5, you need to opt into using the new <Stack.Navigator /> definition. Basically, the <Stack.Navigator /> can have children which are <Stack.Screen /> and anytime you explicitly switch between them (or set them to null), it will animate between them. You can find more documentation on how to do this here: https://reactnavigation.org/docs/auth-flow

There is no createSwitchNavigator in React-Navigation v5.
So it can be implement as below.
App.js
// login flow
const Auth = createStackNavigator();
const AuthStack =()=> (
<Auth.Navigator
initialRouteName="Login"
screenOptions={{
animationEnabled: false
}}
headerMode='none'
>
<Auth.Screen name="Login" component={LoginScreen} />
<Auth.Screen name="Signup" component={SignupScreen} />
</Auth.Navigator>
)
// drawer use only in authenticated screens
const Drawer = createDrawerNavigator();
const DrawerStack = () => (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Settings" component={SettingsScreen} />
<Drawer.Screen name="Logout" component={LogoutScreen}/>
</Drawer.Navigator>
)
const RootStack = createStackNavigator();
class App extends Component {
constructor() {
super();
this.state = {
loading: true,
hasToken: false,
};
}
componentDidMount(){
AsyncStorage.getItem('jwtToken').then((token) => {
this.setState({ hasToken: token !== null,loading:false})
})
}
render() {
const {loading,hasToken} = this.state;
if (loading) {
return <WelcomeScreen/>
}
else {
return(
<NavigationContainer>
<RootStack.Navigator headerMode="none">
{ !hasToken ?
<RootStack.Screen name='Auth' component={AuthStack}/>
:
RootStack.Screen name='App' component={DrawerStack}/>
}
</RootStack.Navigator>
</NavigationContainer>
);
}
}
}
export default App;
This is only one way of doing authentication.I haven't use Redux or React Hooks here.You can see the example which used React Hooks in the React Navigation v5 documentation.

I think I found a solution for this issue which will block all unwanted navigation from the user and still keep smooth transitions between screens.
You need to add navigation.service.js like this:
import * as React from 'react';
export const navigationRef = React.createRef();
export const navigate = (routeName, params) => {
navigationRef.current?.navigate(routeName, params);
}
export const changeStack = (stackName) => {
resetRoot(stackName)
}
const resetRoot = (routeName) => {
navigationRef.current?.resetRoot({
index: 0,
routes: [{ name: routeName }],
});
}
add that ref from navigationService to NavigationContainer like this:
<NavigationContainer ref={navigationService.navigationRef}>
<Navigation />
</NavigationContainer>
now when you know you need to change stack, just call changeStack instead of navigate.
I have explained this solution in more details here:
Change stacks in react-navigation v5

Related

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

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

How to hide some pages from Drawer Navigation but still be able to navigate to them - React Native?

I want to hide some pages from the Drawer
I want to hide some pages from the Drawer (for example hide the SignUpPage and SuccessPage), how can I do it ?
i also tried to make an anonymous function in the DrawerLabel [ ()=> null ] but it is still not a good solution because even tho it shows me an empty label, yet when i click on it , it navigates me to the page that i wanted to hide.
Please help
and thanks for all the helpers :)
import { createDrawerNavigator } from '#react-navigation/drawer';
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="WelcomePage">
//...all the pages
<Drawer.Screen
name="HomePage"
component={HomePage}
options={{ drawerLabel: 'Home Page' }}
/>
<Drawer.Screen
name="SignUpPage"
component={SignUpPage}
options={{ drawerLabel: 'SignUp Page' }}
/>
<Drawer.Screen
name="SuccessPage"
component={SuccessPage}
options={{ drawerLabel: 'SuccessPage' }}
/>
</Drawer.Navigator>
);
}
const Stack = createStackNavigator();
export default function App() {
return (
< NavigationContainer >
<DrawerNavigator>
<Stack.Navigator initialRouterName="WelcomePage">
<Stack.Screen name="WelcomePage" component={WelcomePage} />
>
<Stack.Screen name="SuccessPage" component={SuccessPage} />
<Stack.Screen name="HomePage" component={HomePage} />
</Stack.Navigator>
</DrawerNavigator>
</NavigationContainer >
);
}
You have differents options
I guess you want to hide that options when your user is signed or not. With v5 you can do the code below. The another option is the same but playing with custom content that is a bit complex also I give you the docs if you want the complex solution https://reactnavigation.org/docs/drawer-navigator.
DrawerNavigator
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="WelcomePage">
//...all the pages
<Drawer.Screen
name="HomePage"
component={HomePage}
options={{ drawerLabel: 'Home Page' }}
/>
</Drawer.Navigator>
);
}
AuthNavigator
const Stack = createStackNavigator<AuthParamList>();
export const AuthNavigator = () => {
return (
<Stack.Navigator headerMode='none'>
<Stack.Screen name='SignUpPage' component={SignUpPage}></Stack.Screen>
<Stack.Screen name='SuccessPage' component={SuccessPage}></Stack.Screen>
</Stack.Navigator>
);
};
IsAuthScreen, I use firebase + redux so here you need to put your login logic
const IsAuth: React.FC<RoutesProps> = (props) => {
const { eva, ...rest } = props;
const dispatch = useDispatch();
const onAuthStateChanged = (currentUser: any) => {
console.log("onAuthStateChanged -> currentUser", currentUser)
if (!currentUser) {
dispatch(new authActions.DidTryLogin());
} else {
if (!currentUser.emailVerified) {
dispatch(new authActions.DidTryLogin());
} else {
dispatch(new authActions.SigninSuccess(currentUser));
dispatch(new settingsActions.GetProfile(currentUser.uid));
}
}
};
useEffect(() => {
const subscriber = firebase.auth().onAuthStateChanged(onAuthStateChanged);
return () => {
subscriber();
}; // unsubscribe on unmount
}, [dispatch]);
return (<View >
<LoadingIndicator size='large' /> // Here put a loading component
</View>);
};
App component, I use redux for check if my user is logged so here you need to put your own logic
const isAuth = useSelector(selectAuthUser);
const didTryAutoLogin = useSelector(selectAuthDidTryLogin);
return (
<NavigationContainer>
{isAuth && <DrawerNavigator />}
{!isAuth && didTryAutoLogin && && <AuthNavigator />}
{!isAuth && !didTryAutoLogin && <IsAuthScreen />}
</NavigationContainer>);
So when you logout you don't need to navigate to SignInScreen (this will be a problem if you think about it because if you want to do that you can back to protected screens with the back button or gesture). You only need to update the state and the correct navigator will be in place and you can put the default screen to show. You can achieve this with redux or react context.

Closing Drawer On Logout in React Native

In my React Native app, I have a StackNavigator that handles the login process and a DrawerNavigator which is available for authenticated users.
When I log out of the app and then log back in, I see that the drawer is still open. When I click to log out, I invoke the handleClickLogOut() function in my Navigator component -- see below. I thought by accessing the MainMenuDrawer, I could access closeDrawer() method but I don't see it there.
How can I close the drawer on log out in this code?
Here's the Navigator component:
const LogInStack = new createStackNavigator();
const MainMenuDrawer = createDrawerNavigator();
class Navigator extends Component {
constructor(props) {
super(props);
this.handleClickLogOut = this.handleClickLogOut.bind(this);
}
componentDidMount() {
if (this.props.isAuthenticatedUser && !this.props.isAuthenticated)
this.props.actions.setIsAuthenticated(true);
}
handleClickLogOut() {
removeAuthenticationData()
.then(() => {
return this.props.actions.setIsAuthenticated(false);
});
}
render() {
return (
<NavigationContainer>
{
this.props.isAuthenticated
? <MainMenuDrawer.Navigator drawerContent={(navigation) => <DrawerContent member={this.props.member} navigation={navigation} handleClickLogOut={this.handleClickLogOut} />}>
<MainMenuDrawer.Screen name="Home" component={Dashboard} />
<MainMenuDrawer.Screen name="ToDoList" component={ToDoList} />
</MainMenuDrawer.Navigator>
: <LogInStack.Navigator screenOptions={{ headerShown: false }}>
<LogInStack.Screen name="LoginScreen" component={Login} />
</LogInStack.Navigator>
}
</NavigationContainer>
);
}
}
#Sam here is the solution brother.
import {DrawerActions} from '#react-navigation/native'
then you can call in DrawerContent.js file with this:
navigation.dispatch(DrawerActions.closeDrawer())
Hope this will help you 👐.
Here is my Real Code in DrawerContent.tsx file

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

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