skip to Main Content

The idea is the user should not be able to navigate back to any of the order screens if they use the hamburger menu to navigate to other screens while on any of the order screens.

The Flow:
enter image description here

Given the code…

const handleNavigation = (route: string) => {
    const routes = navigation.getState().routes;
    console.log('routes before:', routes);
    const orderStartIndex = routes.findIndex(route => route.name === navScreens.orderStart.route);
    console.log('orderStartIndex:', orderStartIndex);
    if (orderStartIndex > -1) {
      navigation.dispatch(StackActions.replace(navScreens.orderStart.route, { params: {} }));
      // navigation.getState().routes.splice(orderStartIndex, 1);
    }
    console.log('routes after:', navigation.getState().routes);
    navigation.replace(route);
  };

Expected results after clicking on the settings in the hamburger menu:
enter image description here

I’ve looked at several similar questions, but none give the answer to give these expected results. replace won’t remove the screen in the stack. And splice removes the screen, but also results in a key error crashing the app. pop will only remove the latest screen(s), which would is problematic since the current screen is the hamburger screen and would not finish routing to the selection made.

Edit: adding code for routes and stacks

enter image description here

NavigationConductor.tsx

import React from 'react';
import { NavigationContainer, RouteProp } from '@react-navigation/native';
import { TransitionPresets, createStackNavigator } from '@react-navigation/stack';
import { default as Main } from './Main';
import { Hamburger, HamburgerMenu } from '@app/components';
import { navRoutesToTitles, navScreens } from '@app/utils';
import type { StackParamsList } from '@app/types';

const Stack = createStackNavigator<StackParamsList>();
function getStackHeaderOptions(route: RouteProp<StackParamsList, string>, navigation: any): any {
  const routes = navigation.getState().routes;
  const routeTitle =
    routes[routes.length - 1].name === navScreens.hamburgerMenu.route
      ? navRoutesToTitles[routes[routes.length - 2].name]
      : navRoutesToTitles[route.name];
  const isHamburgerOpen = route.name === navScreens.hamburgerMenu.route;
  return {
    headerTitleAlign: 'center',
    headerStyle: { backgroundColor: '#003069' },
    headerRightContainerStyle: { paddingRight: 20 },
    headerLeftContainerStyle: { paddingLeft: 20 },
    headerTitleStyle: { color: 'hsla(0,0%,100%,.8)' },
    headerTintColor: 'hsla(0,0%,100%,.8)',
    title: routeTitle,
    ...TransitionPresets.ModalFadeTransition,
    headerLeft: () => <Hamburger navigation={navigation} isOpen={isHamburgerOpen} />
  };
}

export default function NavigationConductor() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName={navScreens.home.route}
        screenOptions={({ route, navigation }) => getStackHeaderOptions(route, navigation)}
      >
        {Main(Stack)}
        <Stack.Group>
          <Stack.Screen
            name={navScreens.hamburgerMenu.route}
            component={HamburgerMenu}
            options={{ presentation: 'transparentModal' }}
          />
        </Stack.Group>
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Main.tsx

import React, { ReactNode } from 'react';
import { Home, Items, OrderComplete, OrderReview, OrderSelectItems, OrderStart, Settings } from '@app/screens';
import { navScreens } from '@app/utils';
import type { NavigationStack } from '@app/types';

export default function Main(Stack: NavigationStack): ReactNode {
  return (
    <Stack.Group>
      <Stack.Screen name={navScreens.home.route} component={Home} />
      <Stack.Screen name={navScreens.items.route} component={Items} />
      <Stack.Screen name={navScreens.orderComplete.route} component={OrderComplete} />
      <Stack.Screen name={navScreens.orderReview.route} component={OrderReview} />
      <Stack.Screen name={navScreens.orderSelectItems.route} component={OrderSelectItems} />
      <Stack.Screen name={navScreens.orderStart.route} component={OrderStart} />
      <Stack.Screen name={navScreens.settings.route} component={Settings} />
    </Stack.Group>
  );
}

2

Answers


  1. I’m not sure you’re doing it the right way. But to answer your question…

    • When you want to add a route to the top of the stack and navigate forward to it, use navigation.push().

    • When you only want to navigate forward to it, use navigation.navigate(). If the screen has already been added to the stack, it won’t add it again.

    • If you wanted to reset the stack, use navigation.reset().

    And finally, if you wanted to cancel the hardware back button, you can use BackHandler.

    I’d suggest more to use navigation.navigate() and BackHandler, if you don’t want to change more your code.


    Now, I might give you a more clean way to achieve this…

    It sounds like the user shall follow your steps, and so without being able to go back.

    You might actually use ScrollView.
    Here is a complete example :

    import React, { useEffect, useRef, } from 'react';
    import {  Text, View, Button, ScrollView, useWindowDimensions, BackHandler } from 'react-native';
    
    import { NavigationContainer } from '@react-navigation/native';
    import { createStackNavigator } from '@react-navigation/stack';
    
    const Stack = createStackNavigator();
    
    
    const Order = ({ navigation }) => {
      const carouselRef = useRef(null);
      const { height, width } = useWindowDimensions();
    
      useEffect(() => {
        // You may hide header (which has a back button)
        navigation.setOptions({
          headerShown: false,
        })
        const backAction = () => true; // True = can't go back using hardware
        const backHandler = BackHandler.addEventListener(
          'hardwareBackPress',
          backAction,
        );
    
        return () => backHandler.remove();
      }, []);
    
      const steps = [
        <Text>Hello</Text>,
        <Text>World !</Text>,
      ];
    
      return (
        <ScrollView
          horizontal
          disableIntervalMomentum
          showsHorizontalScrollIndicator={false}
          snapToInterval={width}
          scrollEnabled={false}
          ref={c => carouselRef.current = c}
        >
          {
            steps.map((children, i, a) => (
              <View style={{ width, height }}>
                {children}
                {
                  // No button for final page
                  i < a.length - 1 &&
                  <Button
                    title="Next"
                    onPress={() => carouselRef.current.scrollTo({ y: width * (i + 1) })}
                  />
                }
              </View>
            ))
          }
        </ScrollView>
      );
    }
    
    
    const Home = ({ navigation }) => {
      return (
        <View>
          <Button
            title="Leave"
            onPress={() =>
              navigation.push('Order')
            }
          />
        </View>
      )
    }
    
    export default function App() {
      return (
        <NavigationContainer>
          <Stack.Navigator>
            <Stack.Screen
              name="Home"
              component={Home}
            />
            <Stack.Screen
              name="Order"
              component={Order}
            />
          </Stack.Navigator>
        </NavigationContainer>
      );
    }
    
    Login or Signup to reply.
  2. It will reset the stack and put new route in stack

    import { CommonActions } from ‘@react-navigation/native’;

    navigation.dispatch(
    CommonActions.reset({
      index: 0,
      routes: [{ name: 'RouteName' }], // Ensure 'RouteName' matches your screen's name
    })
    );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search