How to update header bar from drawer screen - react-native

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

Related

Nested Navigation Stack does not find navigator

I am want to add a secondary Navigation so that a subset of screens have access to providers, as opposed to the whole stack being wrapped by providers they do not need access to. However, I think there is something wrong with my nested setup because I get an error when clicking a button that would navigate to the substack:
ERROR The action 'NAVIGATE' with payload {"name":"VehicleDetailInspections","params":{"vehicleId":27541}} was not handled by any navigator.
Do you have a screen named 'VehicleDetailInspections'?
If you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.
The link to this only shows tabs & drawers, which we do not have.
My setup is:
HOMESTACK
const HomeStack = createNativeStackNavigator<HomeStackParams>();
export function HomeNavigator(): JSX.Element {
const styles = useInspectionStyles();
return (
<VehicleDataProvider>
<HomeStack.Navigator
screenOptions={{
headerStyle: styles.headerStyles,
headerBackVisible: false,
headerTitle: '',
headerLeft: () => <GoBack />,
}}
>
<HomeStack.Group
screenOptions={{
headerShadowVisible: false,
headerStyle: [styles.headerStyles],
}}
>
<HomeStack.Screen
name={VEHICLE_DETAIL}
component={VEHICLE_DETAIL.component}
/>
</HomeStack.Group>
/*
// I have tried both of these options:
<HomeStack.Group>
{() => <InspectionNavigator />}
</HomeStack.Group>
// or
<HomeStack.Screen
name={'InspectionSubStack'}
component={InspectionNavigator}
/>
*/
</HomeStack.Navigator>
</VehicleDataProvider>
);
}
In the above HomeStack, in the VEHICLE_DETAIL screen, there is a button which when clicked navigates to VEHICLE_DETAIL_INSPECTIONS which is part of the InspectionStack below.
const navigation = useNavigation<NavigationProp<InspectionStackParams>>();
...
onPress={() =>
navigation.navigate('VEHICLE_DETAIL_INSPECTIONS', {
vehicleId: vehicle.id,
})
}
INSPECTION substack:
const InspectionStack = createNativeStackNavigator<InspectionStackParams>();
export function InspectionNavigator(): JSX.Element {
const styles = useInspectionStyles();
return (
<InspectionsDataProvider>
<InspectionProgresssProvider>
<InspectionStack.Navigator
screenOptions={{
headerStyle: styles.headerStyles,
headerBackVisible: false,
headerTitle: '',
headerLeft: () => <GoBack />,
}}
>
<InspectionStack.Screen
name={VEHICLE_DETAIL_INSPECTIONS}
component={
VEHICLE_DETAIL_INSPECTIONS.component
}
/>
<InspectionStack.Screen
name={VEHICLE_INSPECTIONS_SECTION].name}
component={
VEHICLE_INSPECTIONS_SECTION.component
}
/>
<InspectionStack.Screen
name={VEHICLE_INSPECTIONS_STEP}
component={VEHICLE_INSPECTIONS_STEP.component}
/>
</InspectionStack.Navigator>
</InspectionProgresssProvider>
</InspectionsDataProvider>
);
}
What do I need to do to get the InspectionNavigator to be found by the navigate() from HomeStack?

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

How to avoid the suddenly chop down on react native navigator

Here is the code I got:
I got a Stack Navigator that store two screens:
<Stack.Navigator
initialRouteName="FirstScreen"
screenOptions={{
headerShown: false
}}>
<Stack.Screen name="FirstScreen" component={FirstScreen}/>
<Stack.Screen name="DrawerNavigator" component={DrawerNavigator}/>
</Stack.Navigator>
And I got a DrawerNavigator which have my MainNavigator, which is the screen cap I provided:
<Drawer.Navigator
initialRouteName="MainNav"
screenOptions={{
drawerStyle: {
backgroundColor: getPrimaryColor()
},
drawerLabelStyle: {
color: 'white'
}
}}>
<Drawer.Screen
name="MainNav"
component={MainNav}
options={({route}) => (getShowOptions(route))}/>
</Drawer.Navigator>
Inside the MainNav, it have two page, one is home, another is detail:
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
options={{ headerShown:false }}
name="Home"
component={Home}
/>
<Stack.Screen
name="Detail"
component={Detail} />
</Stack.Navigator>
As you may noticed that it has a getShowOptions method, which is control the actual nav bar display:
function getShowOptions(route) {
const routeName = getFocusedRouteNameFromRoute(route);
const icon =
let options = {
headerStyle: {
backgroundColor: getPrimaryColor()
},
headerTintColor: '#fff',
headerTitleAlign: 'center',
title: i18n.t('Home'),
drawerIcon: ({focused, size}) => (
<View
style={[{
height: iconSize,
width: iconSize
}
]}>
{icon}
</View>
)
};
if (routeName === 'Detail') {
options.headerShown = false;
}
return options;
}
When the screen is push to "Detail", it will hide the header, to show a header that have a back button, but when it put back to "Home", it should use back the "Drawer" menu button. As u can see the issue happen when we switch back and forward between the drawer navigation bar and the stack navigation bar.
The use case is pretty simple, we would like to have the "menu" when the "home" screen is appear, and "back" appear when the "detail" screen is presented. It seems that the React Native Stack Navigator and Drawer Navigator can't work together very well. Any suggestions?

How to disable headers of a parent stack navigator from a screen of the child navigator in React Native?

I am building a React Native app, I am using React Navigation and I have to disable headers of the parent navigator from the screen of a child navigator, so for example
Tab Naviagtor A (Headers ON)
Stack Navigator B (Headers OFF)
Navigtor B Screens ( A, B, C, D, E)
So how do I disable the headers at Tab Navigator A, From a specific Navigator A Screen like C. So that When I navigate to Screen C I no more see the header but will be able to see the header on other screens like E and D.
This should be achievable by using route param like this on the Tab navigator:
<Tab.Navigator
initialRouteName={"A")}
screenOptions={({route}) => {
return {
headerShown: route.name !== "C",
};
}}>
Another option would be to hide the Tab Navigator header and enable Stack Navigator headers on those screens you need.
const StackNavigator = ({ navigation }) => {
return (
<Stack.Navigator
screenOptions={{ headerShown: false }}
>
<Stack.Screen
name="Tabs"
component={TabNavigator}
options={({ route }) => ({
headerTitle: getHeaderTitle(route),
headerLeft: () => (
<NavigationHeaderDashboard navigationProps={navigation} position={'Right'} />
),
headerStyle: {
backgroundColor: '#FFFFFF',
},
headerTintColor: '#092241',
headerTitleStyle: {
fontWeight: 'bold',
fontSize: 20,
alignSelf: 'center',
},
headerTitleAlign: "center",
})}
/>
<Stack.Screen name="ViewScreen" component={ViewScreen} />
</Stack.Navigator>
);
};
I have Drawer (headers ON) -> Tab (headers OFF) -> Screen A, Screen B, Screen C, and I want to hide the drawer header in Screen C. This is how I solved the issue.
Set an id on the drawer navigator
<Drawer.Navigator
id='Drawer' // --> 1.
screenOptions: {{...}}
>
...
</Drawer.Navigator>
In Tab.Screen of C set "unmountOnBlur" to true
<Tab.Navigator
screenOptions={{
headerShown: false,
}}
>
// Other screens ...
<Tab.Screen
name='C'
component={ScreenC}
options={{
unmountOnBlur: true, // --> 2.
}}
/>
</Tab.Navigator>
In the Screen C component, get the parent navigator (drawer) and set screen options "headerShown: false"
export default function ScreenC({ navigation, route }) {
React.useLayoutEffect(() => {
if (!navigation || !route) return
// Get parent by id
const drawerNavigator = navigation.getParent('Drawer')
if (drawerNavigator) {
// Make sure the route name is C before turn header off
if (route.name === 'C') {
drawerNavigator.setOptions({
headerShown: false,
})
}
}
// Turn header back on when unmount
return drawerNavigator
? () => {
drawerNavigator.setOptions({
headerShown: true,
})
}
: undefined
}, [navigation, route])
return ...
}
Hope this help
V0.6
In your Navigtor B Screens, do:
import { useFocusEffect } from '#react-navigation/native';
useFocusEffect(
useCallback(() => {
// Do something when the screen is focused
props.navigation.getParent().setOptions({ headerShown: false})
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
props.navigation.getParent().setOptions({ headerShown: true})
};
}, [])
)

createBottomTabNavigator hide some tabs

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