Closing Drawer On Logout in React Native - 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

Related

React native navigation prop not being passed to the function

React native navigation from stack navigator is not being passed down to the Restaurantsscreen function
This is the function which has to use naviagtion but it cant
export const RestaurantsScreen = ({navigation}) => (
<SafeArea>
<View style={{"backgroundColor": "rgba(255, 255, 255, 255)"}}>
<FlatList
data={[
{ name: 1 }
]}
renderItem={(item) =>{return (
<Pressable onPress={ ()=>console.log(navigation)}>
<RestaurantInfoCard />
</Pressable>
);
}}
and these are the functions where stack navigator is defined
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={Screentwo} /> // this screen is currently loaded and the screentwo is importing the Restaurants screen function inside it as a component
</Stack.Navigator>
</NavigationContainer>
);
}
Kindly help
thanks
You have two solutions :
Past the navigation props from the Screentwo to you're child component RestaurantsScreen
Used the useNavigation hooks directly in your RestaurantsScreen component (see link for this hook : https://reactnavigation.org/docs/use-navigation/)
Example of the first solution:
//Screentwo inherit navigation props direclty, as this component is called in your Stack.Navigator
export const Screentwo = ({navigation}) => {
return(
//As RestaurantsScreen is not called directly in your Stack.Navigator,
//this component will not inherit navigation props,
//you need to past manually navigation props from Screentwo in this way
<RestaurantsScreen navigation={navigation} />
)
}
Example of the second solution with useNavigation :
//Directly in your RestaurantsScreen component
import { useNavigation } from '#react-navigation/native';
//No need to import navigation props
export const RestaurantsScreen = () => {
//Call useNavigation, and navigation can be used in the exact same way as if you past it as a props
const navigation = useNavigation()
return (
<SafeArea>
the rest of your code here
</SafeArea>
)
}

React Navigation : Open drawer when I click on bottom tab navigator

With React Navigation 5, I want to open Drawer when I click on bottom tab navigator (I use material bottom navigator).
I manage to create the bottom tabs buttons and click on them, the home page opens for both tabs (GymIndexScreen or FoodIndexScreen).
When I am on the home pages (GymIndexScreen or FoodIndexScreen), I can open the different Drawers with my fingers (GymDrawerNavigator and FoodDrawerNavigator ) : everything works fine.
Question :
I want the drawers to open / close (toggle) automatically when I click the bottom tabs buttons, without having to open them with my fingers.
App.js :
import { NavigationContainer } from '#react-navigation/native'
const App = () => {
return (
<NavigationContainer>
<BottomTabNavigator />
</NavigationContainer>
)
}
BottomTabNavigator.js :
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs'
const Tab = createMaterialBottomTabNavigator()
const BottomTabNavigator = (props) => {
return (
<Tab.Navigator>
<Tab.Screen
name="Gym"
component={GymDrawerNavigator}
options={{
tabBarLabel: "Musculation",
)}
/>
<Tab.Screen
name="Food"
component={FoodDrawerNavigator}
options={{
tabBarLabel: "Alimentation",
)}
/>
</Tab.Navigator>
)
}
GymDrawerNavigator.js :
import { createDrawerNavigator } from '#react-navigation/drawer'
const Drawer = createDrawerNavigator()
const GymDrawerNavigator = () => {
return (
<Drawer.Navigator>
<Drawer.Screen
name="Gym"
component={GymStackNavigator}
/>
</Drawer.Navigator>
)
}
GymStackNavigator.js :
import { createStackNavigator } from '#react-navigation/stack'
const Stack = createStackNavigator()
const GymStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="GymIndex">
<Stack.Screen
name="GymIndex"
component={GymIndexScreen}
}}
/>
<Stack.Screen
name="GymExerciseIndex"
component={GymExerciseIndexScreen}
}}
/>
... list of screens
If I understood your problem correctly you want to open the drawer automatically when you navigate to the screen?
Add this to the screen components you wish to open the drawer when navigated to.
import {useEffect} from 'react'
...
useEffect(()=>{
navigation.addListener('focus', () => {
// when screen is focused (navigated to)
navigation.openDrawer();
});
},[navigation])``
This answer helped me.
Just use the listeners prop to preventDefault behaviour and then open the drawer.
<Tabs.Screen
name={"More"}
listeners={({ navigation }) => ({
tabPress: e => {
e.preventDefault();
navigation.openDrawer();
}
})}
component={Home}
/>

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.

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

switch navigator in react-navigation v5

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