6

I am using React Navigation 5 in a project, and I'm having trouble trying to block a user from navigating back after a certain point.

The app uses a nested navigation structure similar to this:

ROOT (STACK)
|-- LoginScreens (STACK - options={{ gestureEnabled: false }} )
|   |-- Login (SCREEN) -> when successful navigate to "Home"
|   +-- Register (SCREEN) -> after registration, navigate to "Login"
|
+-- Home (TABS - options={{ gestureEnabled: false }} )
    |-- BlahBlah (SCREEN)
    |-- MyProfile (SCREEN)
    +-- Dashboard (TABS)
        |-- AllTasks (SCREEN)
        +-- SomethingElse (SCREEN)

After a successful user login, the user is sent to the Home screen and should not be able to navigate back to the LoginScreens screen.

I have tried to use the componentDidMount lifecycle method on Home, as well as the useFocusEffect hook, with the following:

  • Placing a callback to React Native's BackHandler, returning true from the handler works (true means back action has been handled, no further back handlers will be called), but it will also block any back navigation within the screens in Home (e.g. I cannot navigate back from Dashboard to MyProfile).
  • Using navigation.reset({ index: 1, routes: [{ name: "Home" }] }). Without index: 1 the navigation just goes back to ROOT's initialRoute (in this case, LoginScreens). With index: 1, a Maximum update depth exceeded error is thrown.
  • Instead navigating directly to Home, I have tried using a navigation.reset() (note: no params, clears the entire navigation history), and after that navigate to the Home screen. This doesn't achieve the desired effect since the current route (ROOT's initialRoute, in this case: LoginScreens) is still pushed on the navigation history before navigating to Home.
  • Combining navigation and reset calls in different ways, I have only managed to get JS angry and throw errors and exceptions at me.

Aaaaand... I have ran out of ideas. Does anyone have any suggestions ?

4

4 回答 4

7

似乎 React Navigation 的文档试图通过本指南涵盖此用例:

https://reactnavigation.org/docs/en/auth-flow.html

那里的例子非常棘手,已经介绍了状态管理库、reducers、React hooks 以及其他任何没有真正帮助的东西。但是,该指南的摘要是:Conditionally render routes.

Unlinke React Navigation 4 和以前的版本,在 React Navigation 5 中你可以有条件地渲染路线。这样一来,您就有效地排除了导航到不存在的路线的任何可能性。下面是一个非常简短的示例,说明如何使用简单的状态变量来实现。但是请记住,此示例仅考虑一次呈现一条路线的导航器。如果您有比本示例中的更多路线呈现的路线,您可能需要调整RootStack.Navigator' 的道具(initialRouteName例如),或显式导航到特定路线。

import React from "react";
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

import LoginNav from "./screens/LoginNav";
import HomeScreens from "./screens/HomeScreens";

const RootStack = createStackNavigator();

export default class MyApp extends React.Component {
    constructor(props){
        super(props);

        this.state = { isLoggedIn: false };
    }

    setIsLoggedIn = (isLoggedIn)=>{ this.setState({ isLoggedIn }); }

    render = () => {
        // Using an arrow function to allow to pass setIsLoggedIn to LoginNav
        // Pass setIsLoggedIn from the props of LoginNav to the screens it contains
        // then from the screens call this function with a true/false param
        const LoginScreens = (props)=> <LoginNav {...props} setIsLoggedIn={this.setIsLoggedIn} />

        return <NavigationContainer style={{ flex: 1 }}>
            <RootStack.Navigator>
                {(this.state.isLoggedIn === false)
                    // If not logged in, the user will be shown this route
                    ? <RootStack.Screen name="LoginScreens" component={LoginScreens} />
                    // When logged in, the user will be shown this route
                    : <RootStack.Screen name="Home" component={HomeScreens} />
                }
            </RootStack.Navigator>
        </NavigationContainer>;
    }
}

在这个例子中,调用(this.) props.setIsLoggedIn(true)渲染Home路由,或者调用false参数返回LoginScreens路由。

希望这个例子比文档中的例子更容易理解。

于 2020-02-19T18:32:34.323 回答
6

好吧,我不得不承认,要找到 v5 的新重置方法的语法并不容易,伙计……ReactNavigation 文档确实需要站点内搜索功能。

无论如何,可以使用重置方法,并且对我来说非常有效。

它看起来像:

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

navigation.dispatch(
  CommonActions.reset({
    index: 0,
    routes: [
      {
        name: 'Home',
        params: { user: 'jane' },
      },
    ],
  })
);

我制作了一个辅助函数,我在我的应用程序的多个地方使用它,它看起来像:

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

export const resetStackAndNavigate = (navigation, path) => {
  navigation.dispatch(CommonActions.reset({ index: 0, routes: [{ name: path }] }));
};
于 2020-04-10T13:50:41.477 回答
0

我喜欢公认的解决方案,但另一种方法是使用 React Context。

const AuthContext = React.createContext();

const setIsLoggedIn = (isLoggedIn) => {
    this.setState({ isLoggedIn }); 
} 

然后包装你的整个导航器:

 <AuthContext.Provider value={setIsLoggedIn}>
     <RootStackNavigator>
        // your screens, etc.
     </RootStackNavigator>
 </AuthContext.Provider>

然后在您的屏幕中,您可以使用:

 const { setIsLoggedIn } = React.useContext(AuthContext);

并在需要时调用它。

请参阅本指南:https ://reactnavigation.org/docs/auth-flow/

于 2022-02-11T16:01:02.623 回答
0

我已经为 react-navigation v5 这样做了:

我创建了一个 CustomDrawerContent-Component 来处理对一个项目的每次按下:

(注:忽略headerfooter属性,这只是我抽屉的调整。)

...
import {
  DrawerContentScrollView,
  DrawerItem,
} from '@react-navigation/drawer';
...

function CustomDrawerContent(props) {
  const {
    state: {routes, index},
    descriptors,
    navigation,
    header,
    footer,
  } = props;
  return (
    <>
      {header}
      <DrawerContentScrollView {...props}>
        {routes.map((route, i) => {
          const focused = i === index;
          const {title, drawerLabel, drawerIcon} = descriptors[
            route.key
          ].options;

          return (
            <DrawerItem
              key={route.key}
              label={
                drawerLabel !== undefined
                  ? drawerLabel
                  : title !== undefined
                  ? title
                  : route.name
              }
              icon={drawerIcon}
              focused={focused}
              onPress={() => {
                navigation.dispatch(
                  CommonActions.reset({index: i, routes: [{name: route.name}]}),
                  // NOTICE: Removes the routes.<name>.state of the Stack to discard
                  // navigation-Position if coming back to it via Drawer-Menu.
                  // If this Stack-State in seeded later on, you can adjust it here needed
                );
              }}
            />
          );
        })}
      </DrawerContentScrollView>
      {footer}
    </>
  );
}

function MainDrawer(props) {
  const {
    screen,
    screen: {initialRoute},
    navigatorProps,
    header,
    footer,
    hideDrawerItems,
  } = props;
  return (
    <Navigator
      initialRouteName={initialRoute}
      {...navigatorProps}
      drawerContent={(drawerProps) => (
        <CustomDrawerContent {...drawerProps} header={header} footer={footer} />
      )}>
      {createMenuEntries(screen, hideDrawerItems)} // that's  only an custom implementation of mine to create <Screen>-Entries. Feel free to replace it with your own
    </Navigator>
  );
}

export default MainDrawer;

魔术至少在这里:

{routes.map((route, i) => {
...
onPress => navigation.dispatch => CommonActions.reset({index: ⇒⇒ i ⇐⇐

当我们映射每条路线时,如果我们点击它,我们使用当前索引和(抽屉项本身的)路线名称来重置它的路线状态。

这对我的目的来说非常有效,因为即使你在抽屉里News ⇒ News Detail"打开抽屉并再次点击News,你也会被传送到你的第一个屏幕News-Stack

在此处输入图像描述

于 2020-05-02T10:59:33.320 回答