Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Navigation 5, block back navigation after login

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 ?

like image 939
cristian Avatar asked Feb 19 '20 13:02

cristian


People also ask

How do I stop going back in react navigation?

To make this work, you need to: Disable the swipe gesture for the screen ( gestureEnabled: false ). Override the native back button in the header with a custom back button ( headerLeft: (props) => <CustomBackButton {... props} /> ).

How do I go back to previous screen in react navigation?

Use the goBack() Method to Go Back One Screen in React Native. The goBack() method is one of the most important methods in the react-navigation library. It allows you to go back to one of the previous screens in your navigation stack.

How do you reset the stack in react navigation 5?

To reset the navigation stack for the home screen with React Navigation and React Native, we can use the navigation. dispatch and the CommonActions. reset methods. import { CommonActions } from "@react-navigation/native"; navigation.


2 Answers

It seems that React Navigation's docs tried to cover this use case with this guide:

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

The example there is very tricky, already introduces state management libraries, reducers, React hooks, and whatever else that doesn't really help. However, the summary of that guide is: Conditionally render routes.

Unlinke React Navigation 4 and previous versions, in React Navigation 5 you can conditionally render routes. In doing so you effectively rule out any possibilities of navigation to an inexistent route. Below, there is a very short example of how you can do it with a simple state variable. Keep in mind however that this example only takes into account a navigator with one route rendered at a time. If you have more routes that are rendered other than the ones in this example, you may need to adjust the RootStack.Navigator's props (initialRouteName for example), or explicitly navigate to a specific route.

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

In this example, call (this.) props.setIsLoggedIn(true) to render the Home route, or call with a false param to return to the LoginScreens route.

Hopefully this example is easier to understand than the one in the docs.

like image 114
cristian Avatar answered Nov 15 '22 03:11

cristian


Well, I have to admit, its was not easy to find the new reset method's syntax for v5, man... ReactNavigation docs really need an in site search functionality.

Anyway, reset method can be used, and worked perfectly for me.

It looks something like:

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

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

I made a helper function which I am using in multiple places in my app, that looks like:

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

export const resetStackAndNavigate = (navigation, path) => {
  navigation.dispatch(CommonActions.reset({ index: 0, routes: [{ name: path }] }));
};
like image 30
Zia Ul Rehman Mughal Avatar answered Nov 15 '22 02:11

Zia Ul Rehman Mughal