German Cocca
German Cocca

Reputation: 849

React navigation - react native - How to block drawer in Stack Navigator nested inside Drawer Navigator?

In my react native app I have a stack navigator nested inside a drawer navigator. I want the drawer to be disabled in the stack navigator pages. I'm using react navigation 6.

In the docs (https://reactnavigation.org/docs/drawer-navigator/#options) I see there are two options for this: gestureEnabled​ and swipeEnabled​. But these can only be used in drawer screens, not in stack screens like my case.

My code is as following:

const Stack = createNativeStackNavigator<RootStackParamList>();
const Drawer = createDrawerNavigator<RootTabParamList>();

const loginStack = () => (
  <Stack.Navigator>
    <Stack.Screen name="LandingScreen" component={LandingScreen} options={{ headerShown: false }} />
    <Stack.Screen name="LoginScreen" component={LoginScreen} options={{ headerShown: false }} />
    <Stack.Screen
      name="RegisterScreen"
      component={RegisterScreen}
      options={{ headerShown: false }}
    />
  </Stack.Navigator>
);

return (
  <NavigationContainer>
    <Drawer.Navigator
      screenOptions={{
        drawerStyle: { backgroundColor: 'white' },
        drawerPosition: 'right',
      }}
    >
      {!user ? (
        <Drawer.Screen
          name="PublicStack"
          component={loginStack}
          // options={{headerShown: false}}
          options={({ route }) => {
            const routeName = getFocusedRouteNameFromRoute(route);
            if (
              routeName === 'LandingScreen' ||
              routeName === 'LoginScreen' ||
              routeName === 'RegisterScreen'
            )
              return { swipeEnabled: false, gestureEnabled: false };
            return { swipeEnabled: true, gestureEnabled: true };
          }}
        />
      ) : (
        <>
          <Drawer.Screen
            name="Search cocktails"
            component={HomeScreen}
            options={{ header: () => <Header /> }}
          />
          <Drawer.Screen
            name="Profile"
            component={ProfileScreen}
            initialParams={{ userParam: null }}
            options={{ header: () => <Header /> }}
          />
          <Drawer.Screen
            name="Publish a recipe"
            component={PublishRecipeScreen}
            options={{ header: () => <Header /> }}
          />
          <Drawer.Screen
            name="Favorites"
            component={FavoritesScreen}
            options={{ header: () => <Header /> }}
          />
          <Drawer.Screen
            name="Published recipes"
            component={PublishedRecipesScreen}
            options={{ header: () => <Header /> }}
          />
          <Drawer.Screen
            name="Log out"
            component={CustomDrawerContent}
            options={{ header: () => <Header /> }}
          />

          <Drawer.Screen
            name="CocktailDetailScreen"
            component={CocktailDetailScreen}
            options={{
              header: () => <Header />,
              drawerLabel: () => null,
              title: undefined,
            }}
          />
        </>
      )}
    </Drawer.Navigator>
  </NavigationContainer>
);

I've tried setting the mentioned options directly on the loginStack drawer screen, like:

<Drawer.Screen
  name='PublicStack'
  component={loginStack}
  options={{swipeEnabled: false, gestureEnabled: false}}} 
/>

But didn't work.

I've also seen this answer (How to disable drawer inside Stack Navigator nested inside Drawer Navigator?) and tried to implement something similar (what my code looks like right now) but still didn't work.

Full code can be found here: https://github.com/coccagerman/mixr

Thanks!

Upvotes: 5

Views: 1410

Answers (3)

Vladimir Vladimirov
Vladimir Vladimirov

Reputation: 319

Francisco's answer is working but it is very laggy and the only solution I found was as follows:

const drawerNavigator = () => {
    <Drawer.Navigator>
      <Drawer.Screen name="DrawerScreen1" component={DrawerScreen1} />
      <Drawer.Screen name="DrawerScreen2" component={DrawerScreen2} />
    </Drawer.Navigator>
};

<Stack.Navigator>
    <Stack.Screen name='DrawerNavigator' children={drawerNavigator} options={{ headerShown: false }} />
    <Stack.Screen name='StackScreen1' component={StackScreen1} />
    <Stack.Screen name='StackScreen2' component={StackScreen2} />
    <Stack.Screen name='StackScreen3' component={StackScreen3} />
</Stack.Navigator>

Basically you insert the Drawer Navigator inside a stack and remove its header.

The downside of that is you have all sub screens (stack screens) listed one below another and grouped in separate Stack Navigators, as it is not a good idea to nest the same type of navigator in one another (e.g. one Stack.Navigator inside another Stack.Navigator).

UPDATE: I found a better solution where I have a lot more control on it and looks much better and logical.

I have screenOptions:{{headerShown: false}} on the Drawer.Navigator and then I manually add the hamburger icon where I need it

<Stack.Screen
  name='StackScreen1'
  component={StackScreenComponent1}
  options={({ navigation }) => 
    ({
      headerLeft: () => {
      <TouchableOpacity onPress={() => navigation.openDrawer()}>
        <Icon name='menu' />
      </TouchableOpacity>
      }
    } as NativeStackNavigationOptions)}
/>

This way I can also control the icon of the hamburger menu, the color, the position (left by default or right if I have a 'right to left' language), etc.

Upvotes: 0

alexanderdavide
alexanderdavide

Reputation: 1675

In my case, I only want to have swipe enabled for the first screen in the stack navigator that's nested in the drawer navigator.

I span a group around the drawer screens, get the focused route name and only enable swipe if it's the first route of the stack navigator.

<Drawer.Group
  screenOptions={({ route }) => ({
    swipeEnabled: getFocusedRouteNameFromRoute(route) === 'NameOfFirstScreenInStack',
  })}
>
  {..}
</Drawer.Group>

getFocuesRouteNameFromRoute isn't well documented but at least there's a guide working with it.

Upvotes: 0

Francisco Gallo M.
Francisco Gallo M.

Reputation: 327

I was stuck with the same thing these days. I didn't find a solution to use getFocusedRouteNameFromRoute(route) and took a different approach.

The first thing is block the Drawer in the whole app:

      <Drawer.Navigator screenOptions = {{ swipeEnabled: false }}>
        <Drawer.Screen name="Screen1" component={StackScreen1} />
        <Drawer.Screen name="Screen2" component={StackScreen2} />
      </Drawer.Navigator>

Then, you enable the Drawer on the screens you need, like this:

      useFocusEffect(
        useCallback((() => {
          // From a Stack screen, the Drawer is accessed.
          const parent = navigation.getParent()
          parent?.setOptions({ swipeEnabled: true })
          // It returns to the initial state.
          return () => parent?.setOptions({ swipeEnabled: false })
        }, [navigation])
      )

In case you have to enable the Drawer on many screens, it can be done the other way around. Enable the Drawer in the whole app, and block it only in the desired ones.

I know that maybe it's not the best solution but I hope it helps you. Saludos!

Upvotes: 5

Related Questions