2

So I've created a navigation using React Navigation (v6) as follows:

  • Home
  • Work - Work.js
    • Work Details - WorkDetails.js

Upon selecting a specific work item from the Work overview screen (Work.js), you're taken to directed to the Word Details screen to see more details. This works as intended, however after doing so, pressing/clicking on "Work" to get back to the overview screen no longer works. Nothing happens from the Work Details page, and if you click home and come back, you simply come back to the details specific page you just left.

In order to navigate to the Work Details screen without having it listed in the primary navigation, I'm using a combination of Drawer and Stack navigators as follows:

App.js

function DrawerMenu() {
  return (
    <Drawer.Navigator 
      useLegacyImplementation 
      screenOptions={{
        headerShown: true, 
        drawerPosition: 'right',
        overlayColor: 'rgba(0,0,0,.5)',              
        drawerActiveTintColor: text,
        drawerInactiveTintColor: text,
        drawerStyle: { backgroundColor: bg, },        
      }}      
      drawerContent={(props) => <CustomDrawerContent {...props} />} >
        <Drawer.Screen name="Home"  component={HomeDrawer}  options={customDrawOptions} />
        <Drawer.Screen name="Work"  component={WorkMenu}    options={customDrawOptions} />
        <Drawer.Screen name="Posts" component={PostsDrawer} options={customDrawOptions} />         
    </Drawer.Navigator>
  );
}

function WorkMenu(){
  return (
    <Stack.Navigator initialRouteName="WorkOverview" 
      useLegacyImplementation
      screenOptions={{
        headerShown: true, 
        drawerPosition: 'right',
        overlayColor: 'rgba(0,0,0,.5)',              
        drawerActiveTintColor: text,
        drawerInactiveTintColor: text,
        drawerStyle: { backgroundColor: bg, },        
      }} >
      <Stack.Screen name="WorkOverview" component={WorkDrawer}       options={{ headerShown: false }} />
      <Stack.Screen name="WorkDetail"   component={WorkDetailDrawer} options={{ headerShown: false }} />
    </Stack.Navigator>  
  );
}

export default function App() {
  return (
    <NativeBaseProvider theme={theme}>
      <NavigationContainer>
        <DrawerMenu />
      </NavigationContainer>
    </NativeBaseProvider>
  );
}

And then from the work overview page:

Work.js

import { useNavigation } from '@react-navigation/native';

function WorkTile({data}) {
    const navigation = useNavigation(); 
    return ( 
        <Pressable mt={5} shadow="2" rounded="lg" w={{ base: 96, md: 72, lg: 48 }} 
            _light={{ bg: "coolGray.50" }} 
            _dark={{ bg: "gray.800" }} 
            overflow={"hidden"}            
            onPress={() => 
                navigation.navigate("WorkDetail", 
                    { 
                        initial: false, 
                        workDetail: data 
                    }
                )
            }
        >
        ...    
        </Pressable>
    );
}

export default WorkTile;

Which just sends the relevant workDetail: data object to WorkDetails.js which then just gets peppered throughout various JSX elements.

The desired behavior is similar to how it would react on the web:
Home -> Work -> Work Details(1) -> Work -> Work Details(2) -> etc.
However it ends up behaving as:
Home <-> Work Details(1)

I've tried adding initial: false when navigating to the WorkDetails.js screen, per the React Navigation docs as well as implementing a reset once on the WorkDetails.js screen, and also updating the config for nested navigation. Most of the similar questions on StackOverflow seem to be a variation of one of those three solutions or seem to be leveraging custom buttons/links (and not automatically generated nav elements in the drawer/header). Regardless, nothing I've tried seems to resolve the issue. Any help would be very much appreciated. Thanks in advance!

Jamie
  • 1,340
  • 11
  • 22

1 Answers1

2

If you need to reset the navigation state of your stack navigator when you navigate away from it, you can achieve this behavior with React Navigation v6 (soon v7), by using the getState and setState methods of the navigation object, as described in the documentation about state persistence.

However, to avoid complexity and keeping the code simple, you can utilize the unmountOnBlur screen option. That is a more straightforward way to achieve the desired behavior, as this will unmount and reset the state of the stack navigator every time you navigate away from it.

The WorkMenu function would be:

function WorkMenu(){
  return (
    <Stack.Navigator initialRouteName="WorkOverview" 
      useLegacyImplementation
      screenOptions={{
        headerShown: true, 
        drawerPosition: 'right',
        overlayColor: 'rgba(0,0,0,.5)',              
        drawerActiveTintColor: text,
        drawerInactiveTintColor: text,
        drawerStyle: { backgroundColor: bg, },
        // The screen will unmount and its state will be reset when you navigate away
        unmountOnBlur: true,        
      }} >
      <Stack.Screen name="WorkOverview" component={WorkDrawer}       options={{ headerShown: false }} />
      <Stack.Screen name="WorkDetail"   component={WorkDetailDrawer} options={{ headerShown: false }} />
    </Stack.Navigator>  
  );
}

That unmountOnBlur option makes the navigator behave similar to how it would on the web, in which navigating away from a page causes its state to reset.

That should give you the behavior you are looking for: Home -> Work -> Work Details(1) -> Work -> Work Details(2) -> etc..
Every time you navigate away from the Work stack navigator, it will unmount and its state will reset, so navigating back to Work will always take you to the WorkOverview screen.


The unmountOnBlur documentation does mention:

Normally, we do not recommend enabling this prop as users do not expect their navigation history to be lost when switching tabs. If you enable this prop, please consider if this will actually provide a better experience for the user.

True: in some scenarios, using unmountOnBlur may not provide the best user experience because it clears the navigation history for that stack. However, based on the behavior you are looking to achieve (i.e., always navigating back to the 'Work' overview screen when selecting 'Work' in the drawer, regardless of how deep you have gone into the 'WorkDetails'), this method would serve your purpose.

If you want to maintain the navigation history for the 'Work' stack but reset it when 'Work' is clicked in the drawer, an alternative could be to create a custom DrawerContent component and handle the onPress for 'Work' manually there. You could then call navigation.reset for the 'Work' stack, which would achieve the same behavior without the side effects of unmountOnBlur.

For instance:

function CustomDrawerContent(props) {
  const navigation = useNavigation();
  return (
    <DrawerContentScrollView {...props}>
      <DrawerItemList {...props} />
      <DrawerItem
        label="Work"
        onPress={() => {
          navigation.dispatch(
            CommonActions.reset({
              index: 0,
              routes: [
                { name: 'Work', 
                  state: {
                    routes: [
                      { name: 'WorkOverview' },
                    ],
                  }, 
                },
              ],
            })
          );
        }}
      />
      {/* Add other drawer items here */}
    </DrawerContentScrollView>
  );
}

// In your Drawer.Navigator
<Drawer.Navigator drawerContent={(props) => <CustomDrawerContent {...props} />}>
  {/* other screens */}
</Drawer.Navigator>

That onPress resets the state of the 'Work' stack to its initial state every time 'Work' is clicked in the drawer. That way, the 'Work' stack will still maintain its state while you are navigating within it, but will reset when you navigate away and come back. Note you have to use this custom onPress only for 'Work' navigation and other items should behave normally.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    Thank you for such a thorough answer and multple approaches! ```unmountOnBlur: true,``` did the trick. Thank you! – Jamie Jul 13 '23 at 02:37