createBottomTabNavigator hide some tabs - react-native

I have following tab navigator defined for react native 6 for a screen, so there are 3 screens in total A, B and C. But I don't want to show "C" since it can be reached by navigating from A.
const Tab = createBottomTabNavigator();
const Main: () => React$Node = props => {
return (
<Tab.Navigator
<Tab.Screen name={Routes.A} component={A} />
<Tab.Screen name={Routes.B} component={B} />
<Tab.Screen name={Routes.C} component={C} />
</Tab.Navigator>
);
};
there has been a solution using style to hide component C. but after upgrading react from 5 to 6, it has stopped to work and I read the documentation and try using new properties without success,
tabBarOptions={{
showLabel: false,
style: {
height: Dimensions.DIMENSION_BOTTOM_TAB_BAR_HEIGHT,
width: '200%',
paddingBottom: 0,
},
visible: false,
}}>
so what is the react 6 way of setting width so component C can be hided? Another question is that (I am new to react), is there any way adding component C into the route for current screen without even having it as <Tab.Screen>. This is the content from ComponentA, the ideal solution here is to add ComponentC here but I tried add component binding in MenuItem without success, it complains there is no such route handled by any navigator. Is is not the same as using createStackNavigator
const Menu: () => React$Node = props => {
return (Colors.COLOR_BLACK]}
<VScrollLayout contentContainerStyle={{flexGrow: 1}} style={styles.content}>
<View style={styles.innerContainer}>
<MenuItem
image={<Image source={Images.ICON_C} />}
text={i18n.t('C')}
route={Routes.C}
navigation={props.navigation}
/>
</View>
</VScrollLayout>
);
};

The correct navigation would be to put the TabNavigation as root (main) navigation and in each tab you have a StackNavigation. If you need to access screen C from both A and B, you can add them in both StackNavigations. (A + C and B + C).
Edit:
code:
const TabAStack = createStackNavigator();
function TabAStackNavigation() {
return (
<TabAStack.Navigator >
<TabAStack.Screen name={Routes.A} component={A} />
<TabAStack.Screen name={Routes.C} component={C} />
</TabAStack.Navigator>
);
}
const TabBStack = createStackNavigator();
function TabAStackNavigation() {
return (
<TabAStack.Navigator >
<TabAStack.Screen name={Routes.B} component={B} />
<TabAStack.Screen name={Routes.C} component={C} />
</TabAStack.Navigator>
);
}
const Tab = createBottomTabNavigator();
const Main: () => React$Node = props => {
return (
<Tab.Navigator
<Tab.Screen name={Routes.TabA} component={TabAStackNavigation} />
<Tab.Screen name={Routes.TabB} component={TabBStackNavigation} />
</Tab.Navigator>
);
};

solve the problem by setting extra width for the tabs that I want to show
<Tab.Screen name={Routes.A} component={A} options={{ tabBarStyle: { width: '500%' } }}/>
<Tab.Screen name={Routes.B} component={B} options={{ tabBarStyle: { width: '500%' } }}/>
<Tab.Screen name={Routes.C} component={C} options={{ tabBarStyle: { width: '200%' } }}/>
I totally understand it is an ugly solution, but it will stay before I learn how to add ComponentC into VScrollLayout

Related

React Native Drawer Navigation show headerLeft

I have a Drawer navigator which is inside a Stack navigator and I'd like to display a header. Currently I can display everything I want however because the header is defined at the Stack level the navigation inside the header is stack level not drawer level which is preventing me from opening the drawer.
Root stack
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ auth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
headerRight: () => (
<HeaderRight navigation={ navigation } />
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
Drawer stack
<Drawer.Navigator options={{
headerShown: true,
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
}}>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ChatListScreen}
/>
<Drawer.Screen
name="ChatRoom"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null
}}
component={ChatRoomScreen}
/>
</Drawer.Navigator>
Note in the drawer navigator the line with headerLeft does nothing and is there to show where I attempted to put it thinking it would work. I did think it might be overlaying the stack one so I commented out the stack one and it didn't work.
HeaderLeft
export default function HeaderLeft ({ navigation }) {
const openMenu = () => {
navigation.toggleDrawer();
}
return (
<View style={styles.header}>
<Icon name='menu' onPress={openMenu} size={28} style={styles.icon} color="white"/>
</View>
)
}
My question is how can I refactor this to enable me to have the HeaderLeft component work to open the drawer. I will be adding more screens so ideally something I don't have to pass to each screen but if that is what works I am good with it too.
Options in DrawerStack not work. I modified it:
<Drawer.Navigator
screenOptions={{
headerLeft: () => <HeaderLeft />,
}}>
// ...
</Drawer.Navigator>
And a little change in HeaderLeft:
import { useNavigation } from '#react-navigation/native';
function HeaderLeft() {
const navigation = useNavigation();
const openMenu = () => {
navigation.toggleDrawer();
};
// render your Button
}
Demo: https://snack.expo.dev/#pqv2210/0d613b

react navigation drawer reopen after navigate to screen

I'm working on a simple app and have now added drawer navigation, but I'm having a weird problem with the drawer that opens again after navigating to the screen from the drawer content.
PlayerZone is a BottomTabNavigation that contains Profile and other screens.
I tried to dispatch close drawer action before and after navigate to screen, also I tried requestAnimationFrame but nothing helps.
how it looks like
DrawerScreens:
<Animated.View style={[styles.stack, style]}>
<Stack.Navigator
screenOptions={{
header: () => null,
}}>
<Stack.Screen name="PlayerZone" component={PlayerZone} />
</Stack.Navigator>
<GameBottomNavigation />
</Animated.View>
Drawer:
<DrawerItem
label="Profile"
style={styles.item}
labelStyle={styles.itemLabel}
onPress={() => {
props.navigation.navigate('PlayerZone', {
screen: 'Profile',
id: null,
});
}}
/>
<DrawerItem
label="Items"
style={styles.item}
labelStyle={styles.itemLabel}
onPress={() => {
props.navigation.navigate('PlayerZone', {
screen: 'Profile',
id: 'some-id',
});
}}
/>
PlayerZone:
<Tab.Navigator
initialRouteName="Profile"
screenOptions={{
header: () => null,
}}>
<Tab.Screen name="Profile" component={Profile} />
{!peeking && <Tab.Screen name="Items" component={Items} />}
</Tab.Navigator>
Items:
import React, {FC} from 'react';
import {BottomTabScreenProps} from '#react-navigation/bottom-tabs';
import Block from '../../components/Block';
import Text from '../../components/Text';
import {PlayerZoneTabParamList} from '.';
type Props = BottomTabScreenProps<PlayerZoneTabParamList, 'Items'>;
const Items: FC<Props> = () => {
return (
<Block middle center>
<Text>Items</Text>
</Block>
);
};
export default Items;
So after some time trying everything I could think of I found solution (kind of). In drawer navigator I had props useLegacyImplementation set to false because I have configured reanimated 2. I removed this props and everything start working properly.

react-navigation [v6] drawer and stack nesting issues

Like the most common use case, I want to have a drawer in my react-native application where, from all screens header I can open/close the drawer, navigate to them. Also, each screen has an option (button, card,...) that on press should navigate to any other screen.
So I have defined the following structure. But there are several problems;
Drawer cannot recognize which screen in on focus now.
Drawer type check does not give auto-suggestions on navigation prop (e.g. props.navigation.navigate(" /* no suggestion on the present screens in the stack*/"))
On Android I feel that the overall performance drops significantly
So is this a good structure? From the official documentation, I could not find any hint on how to implement it? stack nested in drawer or vise-versa?
export type MainStackParamList = {
HomeScreen: undefined;
OverViewScreen: undefined;
WorkOrdersScreen: {id?: number; description?: string; tabIndex?: number};
PropertiesScreen: undefined;
PropertyDetailScreen: {propertyUnit: PropertyUnit};
};
export type MainDrawerParamList = {MainStack: NavigatorScreenParams<MainStackParamList>};
export type AppNavigationCompositeProps = CompositeScreenProps<
DrawerScreenProps<MainDrawerParamList, 'MainStack'>,
StackScreenProps<MainStackParamList>
>;
//____The navigation part______
const MainStack = createStackNavigator<MainStackParamList>();
const Drawer = createDrawerNavigator<MainDrawerParamList>();
/* the composite type is the only way I found to have access to
drawer fucntions such as toggleDrawer in the stack screens*/
const MainStackScreens = (navigation: AppNavigationCompositeProps) => (
<MainStack.Navigator initialRouteName={'HomeScreen'} screenOptions={MainStackScreenOptions(navigation)}>
<MainStack.Screen name="HomeScreen" component={HomeScreen} />
<MainStack.Screen name="OverViewScreen" component={OverViewScreen} />
<MainStack.Screen name="WorkOrdersScreen" component={WorkOrdersScreen} />
<MainStack.Screen name="PropertiesScreen" component={PropertiesScreen} />
<MainStack.Screen name="PropertyDetailScreen" component={PropertyDetailScreen} />
</MainStack.Navigator>
);
const Navigation: React.FC<{}> = () => {
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="MainStack"
screenOptions={{headerShown: false}}
drawerContent={props => <CustomDrawerContent {...props} />}>
<Drawer.Screen name="MainStack" component={MainStackScreens} />
</Drawer.Navigator>
</NavigationContainer>
);
};
export default Navigation;
const MainStackScreenOptions = (navigation: AppNavigationCompositeProps): StackNavigationOptions => {
return {
headerStyle: {backgroundColor: '#00aade'},
headerTintColor: '#fca903',
headerTitleAlign: 'center',
headerTitleStyle: {fontWeight: 'bold', fontStyle: 'italic'},
headerBackTitle: 'GoBack',
headerLeft: () => <IconButton icon="menu" color="white" onPress={() => navigation.navigation.openDrawer()} />
};
};
//___the drawer content is like
const CustomDrawerContent: React.FC<DrawerContentComponentProps> = props => {
return (
<DrawerContentScrollView>
<Drawer.Item label="Home" onPress={() => props.navigation.navigate('HomeScreen')} icon="star" />
<Drawer.Item label="OverView" onPress={() => props.navigation.navigate('OverViewScreen')} icon="star" />
<Drawer.Item label="WorkOrders" onPress={() => props.navigation.navigate('WorkOrdersScreen')} icon="star" />
<Drawer.Item label="Properties" onPress={() => props.navigation.navigate('PropertiesScreen')} icon="star" />
</DrawerContentScrollView>
);
};

React Navigation Tab.Screen set Component as name

What i want to do is the following when using the react navigation package.
The docs allow the following:
<Tab.Screen name="name" component={FeedScreen} />
what i want to achieve is this:
<Tab.Screen name={<NameMarkdown />} component={FeedScreen} />
because i want to render markdown as the name of a tab in my application.
is there a way to solve the problem?
The name prop only accepts the string name of the screen you can't pass the component into a name prop.
To give customize the style of your tab you can use it like this
<Tab.Navigator
screenOptions={tabScreenOptions}
tabBarOptions={tabBarOptions}
>
<Tab.Screen name="name" component={FeedScreen} />
tabBarOptions = {
showLabel: false,
style: styles.tabContainer,
tabStyle: styles.tabStyle,
};
tabScreenOptions = props => {
const { route } = props;
return {
tabBarIcon: ({ focused }) => <TabIcon {...{ route, focused }} />,
};
};
const TabIcon = ({ focused, route }) => (
// Here you can use your own component according to your need
<Image
source={Images.tabs[route.name]}
style={{ tintColor: focused ? Colors.primary : Colors.white214 }}
/>
);
Hope this helps you out.

How to update header bar from drawer screen

I am using nested navigation. The root navigator is a StackNavigator and child is DrawerNavigator as far as I know there is no way to put a header bar via DrawerNavigator.
So I made it via StackNavigator but I can not update the header title when I navigate a screen that in the DrawerNavigator. How can I update header title in a DrawerScreen
Root Navigator:
<Stack.Navigator screenOptions={screenOptions} initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
// ...
<Stack.Screen // This is the screen that contains a child navigator
name="Main"
component={Main}
options={({navigation}) => ({
title: 'Main Page',
headerLeft: () => <MenuIcon stackNavigation={navigation} />,
})}
/>
</Stack.Navigator>
Child Navigator:
<Drawer.Navigator>
//...
<Drawer.Screen
name="Bids"
component={Bids}
options={{
title: text.bids, // This updates the title that in the drawer navigator menu not the header bar title.
headerTitle: () => <SearchBar />, //Also this doesn't work I can not update header bar specific to a DrawerComponent.
}}
/>
//...
</Drawer.Navigator>
I try to pass navigation prop of Stack Screen to Drawer Screens but I could not find any way to do this.
<Drawer.Screen
component={<Bids stackNavigation={navigation} />} // This does not compile :(
//...
/>
I try to use setOptions:
const Bids = ({navigation}) => {
navigation.setOptions({title: 'Bids'});
//...
};
But again it updates the title in the drawer menu not the Header Bar title.
How can I update Header Bar from a Drawer Screen?
I am able to get it. I have the following setup.
First -> A Stack = Pre-Home + Home tabs
Second -> Bottom tabs
Third -> Stack for individual tabs, lets call it inner stack
So, i have a Stack <-> Bottom Tabs <-> Inner Stack.
On Screen change of inner stack, i need to update header which is part of my Parent Stack/Root Stack.
This can be done with the following in the Parent Stack. In your root/Parent Stack have the below
<Stack.Screen
name="Tabs"
component={MyTabs}
options={({ route, navigation }) => {
let currStackState = [];
let currScreenName = null;
if (route.state) {
currStackState = route.state.routes[route.state.index].state;
const currIndex = currStackState.index;
currScreenName = (currStackState.routes[currIndex]).name;
}
let headerForScreen = getHeaderForRoute(currScreenName);
// further common things if you want to add can add to above object headerForScreen
return headerForScreen;
}}
/>
your function to header can look like this
const getHeaderForRoute = (routeName, stackName) => {
//just kept stackName for future purpose, not using it.
switch (routeName) {
case 'Scree1':
case 'MoreScreens':
case '':
return {
headerShown: true,
headerStyle: DeviceInfo.hasNotch()
? [styles1.headerBGColor, styles1.withNotch] : styles1.headerBGColor,
headerTitle: '',
headerLeft: () =>
// Reactotron.log("route", route);
(
<View style={{
flex: 1, marginLeft: 18, flexDirection: 'row',
}}
>
{/* your component */}
</View>
),
headerRight: () => (
<View style={{ marginRight: 15, flexDirection: 'row' }}>
{/* your code*/}
</View>
),
};
case 'SomeOther':
return {
headerShown: true,
headerTitle: 'SomeTitle'
};
default:
return {};
}
};