Nested Navigation Stack does not find navigator - react-native

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?

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

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})
};
}, [])
)

Can we have different custom drawer contents

I have to Drawer Screens. Both require custom drawer content. I like to know if we can have one custom drawer content for HomeDrawer and a completely different drawer content for the PanelDrawer
<Drawer.Navigator
initialRouteName="HomeDrawer"
edgeWidth={0}
// swipeEdgeWidth={0}
drawerContent={props => <DrawerContent {...props} />}>
<Drawer.Screen
name="HomeDrawer"
options={{
title: headerTitleEnabled ? 'Hello App' : '',
headerStyle: {
backgroundColor: Colors.primary,
},
headerTintColor: '#fff',
headerRight: props => <HeaderButtons {...props} />,
}}
component={MainTabNavigator}
/>
<Drawer.Screen
name="PanelDrawer"
options={{
title: headerTitleEnabled ? 'Hello App' : '',
headerStyle: {
backgroundColor: Colors.primary,
},
headerTintColor: '#fff',
headerRight: props => (
<HeaderButtonsCustom{...props} />
),
}}
component={CustomLayoutNavigator}
/>
Of course, you can nest the drawers if that's the case you want:
Nesting navigators means rendering a navigator inside a screen of another navigator
const MainDrawer = createDrawerNavigator();
const HomeDrawer = createDrawerNavigator();
const PanelDrawer = createDrawerNavigator();
const HomeScreen = () => (
<HomeDrawer.Navigator>
{/*Home drawer custom stuff*/}
</HomeDrawer.Navigator>
);
const PanelScreen = () => (
<PanelDrawer.Navigator>
{/*Panel drawer custom stuff*/}
</PanelDrawer.Navigator>
);
const Navigator = () => (
<MainDrawer.Navigator>
<MainDrawer.Screen name="HomeScreen" component={HomeScreen} />
<MainDrawer.Screen name="PanelScreen" component={PanelScreen} />
</MainDrawer.Navigator>
);
You can read more about nesting navigators in the official documentation.

For react-navigation + web, directly navigating to a nested route via URL results in missing back button in the header

I have HomeScreen with a link that goes to DeckScreen. When I click a button to navigate to the DeckScreen, the back button in the header bar shows up fine.
But when I reload the page in browser or directly navigate to this URL (localhost/deck), there is no back button.
And clicking on the BottomTab doesn't do anything, will not take us back Home.
I am using BottomTab that has a HomeStack, which contains the HomeScreen and DeckScreen.
export default function Navigation () {
return (
<NavigationContainer linking={linking} theme={DefaultTheme}>
<RootNavigator/>
</NavigationContainer>
);
}
function RootNavigator () {
return (
<Stack.Navigator>
<Stack.Screen name='Root' component={Nav} options={{headerShown: false, ...fade}}/>
<Stack.Group screenOptions={{presentation: 'modal'}}>
<Stack.Screen name='Modal' component={ModalScreen}/>
</Stack.Group>
</Stack.Navigator>
);
}
function HomeStackScreen () {
return (
<HomeStack.Navigator initialRouteName='dashboard'>
<HomeStack.Screen name='dashboard' component={HomeScreen} options={{headerShown: false, title: 'Dashboard'}}/>
<HomeStack.Screen name='deck' component={DeckScreen} options={{title: 'Deck'}}/>
</HomeStack.Navigator>
);
}
function Nav ({navigation}) {
return (
<BottomTab.Navigator
initialRouteName='home'
screenOptions={{
headerShown: false,
}}>
<BottomTab.Screen
name='home'
component={HomeStackScreen}
})}
/>
</BottomTab.Navigator>
);
}
And here is my Linking:
const linking: LinkingOptions<RootStackParamList> = {
prefixes: [Linking.makeUrl('/')],
config: {
screens: {
Root: {
screens: {
home: {
screens: {
dashboard: 'dashboard',
deck: 'deck'
},
}
},
}
}
}
};
I've tried using getStateFromPath to try to inject a route in stack but it doesn't work and feels wrong.
How do you tell React Navigation, this screen is part of a stack, and it should always have a back button in that header?
The reason why there's no back button when you're opening from the link is most likely because you don't set headerLeft in the screen and there's no other screen in the navigation stack (you went directly to the DeckScreen).
You can set the back button in the option in Screen, like this example below:
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
),
}}
/>
</Stack.Navigator>
);
}
You can find the example here

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