React Native navigation 6 (Expo) - how can I toggle the drawer tab from the header of my Tabs navigation? - react-native

Hi I have react native (using expo) navigation 6. I am trying to have Tabs (Bottom) navigation and drawer navigation. I want to have the drawer nav hamburger menu inside the header. As far as I can see its being done having the following
headerLeft: (props) => {
return <Button
title="yes"
onPress={() => navigation.toggleDrawer() } />
}
either inside screenOptions or options (of the Tabs navigation). I kept it in screenOptions as I want it to be visible on all screens. However whatever I tried so far I am always getting an err saying either
undefined is not an object (wenn I pass in an object to screenOption (please see full code below)
or
navigation.ToggleDrawer is not a function - when I pass a function to screenOptions (please see full code below). I cannot find any solution or understand what I am doing wrong. Any help would be great!
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
return (
<Drawer.Navigator>
<Drawer.Screen name="Test" component={Test} />
<Drawer.Screen name="s" component={SearchScreen} />
</Drawer.Navigator>
)
}
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator
screenOptions={({ navigation }) => ({ //here I get "undefined is not an object"
headerLeft: (props) => {
return <Button
title="yes"
onPress={() => navigation.toggleDrawer() } />
}
})}
/*screenOptions={{ // here I would get "navigation.ToggleDrawer is not a function"
}}*/
>
<Tab.Screen name="Home" component={HomeScreen}/>
<Tab.Screen name="Tab1" component={Tab1} />
<Tab.Screen name="Tab2" component={Tab2} />
</Tab.Navigator>
);
}
const Navigator = () => {
return (
<NavigationContainer>
<MyTabs />
</NavigationContainer>
)
}
export default Navigator;

Related

How to handle authentication on multiple navigators (react-navigation) in React Native?

Here is my tab navigator present in MainNavigator.js,
const MainNavigator = () => {
return (
<Tab.Navigator>
<Tab.Screen
name={'Home'}
component={Home} />
<Tab.Screen
name={'About'}
component={About} />
<Tab.Screen
name={'Profile'}
component={Profile} />
</Tab.Navigator>
)
}
I have an AuthNavigator.js which includes a stack navigator,
const AuthNavigator = () => {
return (
<Stack.Navigator>
<Stack.Screen name={'Login'} component={Login} />
<Stack.Screen name={'Signup'} component={Signup} />
</Stack.Navigator>
}
So, I have a logout button on my Profile Screen in Tab Navigator. I want to Navigate to Login Screen in AuthNavigator with the onPress event. Here is how I tried this,
const Profile = ({navigation}) => {
const logoutHandler = () => {
navigation.navigate('Login');
}
<SafeAreaView>
<TouchableOpacity onPress={logoutHandler}>
<Text>Logout</Text>
</TouchableOpacity>
</SafeAreaView>
}
However, this is not working. As a beginner to React Native I have no idea how to link this Login route into a Stack screen. Really appreciate it if somebody could save my day. Thankyou so much.
Manage 1 state globally (using redux or some other state management tool) and that is isAuthenticated.
Then in your App.js or where ever you have defined your root navigator do the changes likewise : -
<NavigationContainer>
{ isAuthenticated ? <AuthRoutes /> : <UnAuthRoute />
</NavigationContainer>
On login and logout you'll have to update this isAuthenticated state and then routes will be taken care as per the state

UI Kitten and initialRouteName

I am trying to set a specific tab to start the application. UI kitten's navigation, starts up with the first tab, and actually I want the tab that is in the second position to be the first to appear to the user. I can not find how to set the initialRoute withtin UI kittens Bottom tab bars. I post some of my code so it can be clear:
const { Navigator, Screen } = createBottomTabNavigator();
const BottomTabBar = ({ navigation, state }) => (
<View>
<Divider />
<BottomNavigation
appearance="noIndicator"
selectedIndex={state.index}
onSelect={(index) => navigation.navigate(state.routeNames[index])}
>
<BottomNavigationTab title="screen1" icon={icon1} />
<BottomNavigationTab title="screen2" icon={icon2} />
<BottomNavigationTab title="screen3" icon={icon3} />
</BottomNavigation>
</View>
);
export const BottomTabsNavigator = () => (
<Navigator tabBar={(props) => <BottomTabBar {...props} />}>
<Screen name="screen1" component={Screen1}/>
<Screen name="screen2" component={Screen2}/>
<Screen name="screen3" component={Screen3} />
</Navigator>
);
export const AppNavigator = () => {
return (
<SafeAreaView>
<NavigationContainer>
<Navigator headerMode='none' >
<Screen name={'BottomTabs'} component={BottomTabsNavigator} />
</Navigator>
</NavigationContainer>
</SafeAreaView>
)
};
I've tried in multiple positions but it doesn't seem to work.
Within the following component:
<Navigator tabBar={(props) => <BottomTabBar {...props} />}>
You need to use the following prop: initialRouteName, like so:
<Navigator initialRouteName={'namehere'} tabBar={(props) => <BottomTabBar {...props} />}>
This is telling the navigator what screen to start on when initially rendered! Hope this helps.
For anyone outside of this question that has landed on this page, you can specify what screen you want the navigator to "start" on by using the prop: initialRouteName. This then tells the navigator to use this as your landing page!

How to propagate react navigation params to multiple Screens sharing the same Navigator

I'm trying to work with the react-native and react-navigation library. I'm trying to pass a param to multiple screens sharing the same Tab Navigator.
This is the scenery:
App.js
const MainScreen = ({ navigation }) => {
return (
<Button
onPress={() =>
navigation.navigate("User", { prop_i_want_to_share: "asd" })
} //This is the param I would like to share
/>
);
};
const UserInfoScreen = ({ route }) => (
<View>
{route.params.prop_i_want_to_share} // Here I would like to get the param used in navigation
</View>
);
const UserEditScreen = ({ route }) => (
<View>
{route.params.prop_i_want_to_share} // Here I would like to get the param used in navigation
</View>
);
const UserTab = () => (
<Tab.Navigator>
<Stack.Screen name="UserInfo" component={UserInfoScreen} />
<Stack.Screen name="UserEdit" component={UserEditScreen} />
</Tab.Navigator>
);
render() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Main" component={MainScreen} />
<Stack.Screen name="User" component={UserTab} />
</Stack.Navigator>
</NavigationContainer>
);
}
Basically, I'm trying to propagate the params, but I think I didn't design properly the navigator structure and that's why I'm finding this issue. What would you change to make this possible?
See the answer for this related question.
Depending on what version of react-navigation you're using (I'll assume 6.x), to pass to a nested navigator you could use:
In MainScreen
navigation.navigate('User', {
screen: 'UserInfo',
params: { prop_i_want_to_share: "asd" },
});
Then you can pass the param to the next screen once you navigate to it:
In UserInfoScreen
navigation.navigate('User', {
screen: 'UserEdit',
params: { prop_i_want_to_share: route.params.prop_i_want_to_share },
});
But if you're trying to update a global variable, that is a variable that gets used in multiple screens (ex: CurrentlyLoggedInUserId), you might not want to use params at all. You could use a state management tool like Recoil, Redux, React's useContext or even create your own custom hook.
I have not tried it yet, but with latest v6, you can use Groups:
<Stack.Navigator>
<Stack.Group screenOptions={({ route, navigation }) => ({ prop_i_want_to_share: 'asd' })}>
{/* screens */}
</Stack.Group>
</Stack.Navigator>

navigation.navigate on a button in headerRight of the Stack Navigator

Firstly, I have the same issue as Button linking in headerRight. But their solution was simply using functional over component. I cannot simply switch to functional code as I need to use componentDidMount, so I really need solution for a component based headerRight navigation usage.
Stack
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen
name="Root"
component={BottomTabs}
options={{
headerRight: ({ navigation }) => (
<View>
<Button
onPress={() => navigation.navigate('Profile')}
</Button>
</View>
),
}}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
BottomTabs
const BottomTabs = createBottomTabNavigator();
function MyTabs() {
return (
<BottomTabs.Navigator
...
This will bring an error that navigation is not available there. Okay that's right, as you cannot use navigation directly in the definition of the Stack Navigator.
Even using:
headerRight: () => {
return <ProfileButtonScreen/>
},
did not help as on that component I still not have the navigation available.
This is too less info but is already going in the right direction. And finally this gave me the idea about misusing the BottomTabs for the defining of the headerRight.
Stack
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen
name="Root"
component={BottomTabs}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
BottomTabs
const BottomTabs = createBottomTabNavigator();
function MyTabs({ navigation, route }) {
navigation.setOptions({
headerRight: () => (
<View>
<Button
onPress={() => navigation.navigate('Profile')}
title="To Profile"
>
</Button>
</View>
),
});
return (
<BottomTabs.Navigator
...
This will now let you have a clickable button on stack navigation header.

Reset stack navigation when tab button is pressed in ReactNav 5

I have 3 tabs and behind each tab is a stack navigation. I always want to reset the stack navigation when I click on another tab button.
Right now, when I go in Stack1 like A -> B -> C -> D
and I change to Tab2 and then change back to Tab1, I am again at Screen D.
I want to see Screen A again. I use React-Navigation-5
I would accept any answer that shows me a piece of code how to implement that.
My code looks like this:
App.js:
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Tab1" component={Stack1} />
<Tab.Screen name="Tab2" component={Stack2} />
<Tab.Screen name="Tab3" component={Stack3} />
</Tab.Navigator>
</NavigationContainer>
);
}
where as each of my stack navigations look like this:
function EventExploreStack({ navigation }) {
return (
<SettingsStack.Navigator initialRouteName="A">
<SettingsStack.Screen name="A" component={AScreen} />
<SettingsStack.Screen name="B" component={BScreen} />
<SettingsStack.Screen name="C" component={CScreen} />
<SettingsStack.Screen name="D" component={DScreen} />
<SettingsStack.Screen name="E" component={EScreen} />
</SettingsStack.Navigator>
);
}
export default EventExploreStack;
I am using React Navigation 5.
One option would be to use the reset action of the navigation. As you are having three stacks in three tabs you will need a custom tabbarbutton to do this which will reset the state of the given tab. The code for the button will be as below.
Here I've used Home and Settings as tabs, you will have to change them to your need.
const CustomButton = (props) => {
const navigation = useNavigation();
return (
<TouchableOpacity
{...props}
onPress={() => {
navigation.dispatch((state) => {
const newState = Object.assign(state);
const index = newState.routes.findIndex((x) => (x.name = 'Home'));
if (newState.routes[index].state) {
const { name, key } = newState.routes[index];
newState.routes[index] = { name, key };
}
return CommonActions.reset({
...newState,
});
});
navigation.navigate('Settings');
}}
/>
);
};
Which you can place in the tab screen as below
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarButton: (props) => <CustomButton {...props} />,
}}
/>
You can tryout the sample here
https://snack.expo.io/#guruparan/bottomnavclick
Hope this helps :)
Take a look at the **createBottomTabNavigator** here https://reactnavigation.org/docs/bottom-tab-navigator/
Example
npm install #react-navigation/bottom-tabs
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}
Create a helper that reset the stacks of all Tabs that are not currently selected, and pass it to every tab.
Like this:
Helper:
import { StackActions } from '#react-navigation/native';
export const resetStacksOnTabClicks = ({ navigation }: any) => ({
tabPress: (e: any) => {
const state = navigation.dangerouslyGetState();
if (state) {
const nonTargetTabs = state.routes.filter((r: any) => r.key !== e.target);
nonTargetTabs.forEach((tab: any) => {
if (tab?.state?.key) {
navigation.dispatch({
...StackActions.popToTop(),
target: tab?.state?.key,
});
}
});
}
},
});
Then pass that helper to every Tab in the listeners prop like this:
<Tabs.Screen
name="TabName"
component={YourComponent}
listeners={resetStacksOnTabClicks}
/>