skip to Main Content

I have a react-native app that stores login credentials in a jwt. My logout function looks like this:

              logOut={() => {
                AsyncStorage.getAllKeys()
                  .then((keys) => AsyncStorage.multiRemove(keys))
                    .then(() =>
                        navigation.reset({
                          index: 0,
                          routes: [{ name: 'Login' }],
                          key: null,
                      }),
                    )

My StackNavigator (in App.jsx) looks like this:

       <NavigationContainer >
        <Stack.Navigator>
          {valid_token ? (
            consented ? ( 
              <>
              <Stack.Screen name="Home" component={Home} />
              <Stack.Screen name="Nav" component={Nav} /> // Nav is the component with the login function
          </>
            ): (
              <>
              <Stack.Screen name="ConsentForm" component={ConsentForm} />
              </>
            )
          ) : (
            <>
            <Stack.Screen name='Login'>
                  {(props) => <Login setExpired={setValidToken} setConsented={setConsented} />}
            </Stack.Screen>
            </>
          )}
        </Stack.Navigator>
      </NavigationContainer>

valid_token is set based on the value of the jwt, which is in async storage. I am clearing out the token in the logout function, however, App.jsx doesn’t see that. I also am not sure if I really have to pass the setValidToken and setConsented props throughout the whole app (the nav bar, which contains the logout, is on every screen). When I don’t pass props and run the code as is, I get this error:

The action 'RESET' with payload {"index":0,"routes":[{"name":"Login"}],"key":null} was not handled by any navigator.

I tried replacing the navigation.reset part of the logout function with navigation.dispatch(StackActions.popToTop()) – this takes me to the Home page.

Is passing props throughout the whole app my only option?

2

Answers


  1. The issue you’re facing is a common one when dealing with authentication and navigation in React Native. The problem arises because the navigation state isn’t being updated when the token is removed from AsyncStorage. Here are a few suggestions:

    1. Centralize your authentication state: Instead of passing setValidToken and setConsented props throughout your app, consider using a centralized state management solution like Redux or React’s Context API. This way, you can update the authentication state from anywhere in your app and have it immediately reflected everywhere.

    2. Use an authentication flow: Consider setting up an authentication flow as suggested in the React Navigation documentation. This involves creating a separate stack navigator for authentication-related screens (like Login) and another for the main app screens. You would then conditionally render one or the other based on your authentication state.

    3. Reset the navigation state correctly: The error message suggests that the ‘Login’ route doesn’t exist in your navigator when you’re trying to reset to it. Make sure that the route names you’re using with navigation actions correspond to the name props of your Screen components.

    Here’s an example of how you might set up your navigators:

    // AuthNavigator
    const AuthNavigator = createStackNavigator();
    <AuthNavigator.Navigator>
      <AuthNavigator.Screen name="Login" component={Login} />
    </AuthNavigator.Navigator>
    
    // AppNavigator
    const AppNavigator = createStackNavigator();
    <AppNavigator.Navigator>
      <AppNavigator.Screen name="Home" component={Home} />
      <AppNavigator.Screen name="Nav" component={Nav} />
    </AppNavigator.Navigator>
    
    // RootNavigator
    const RootNavigator = () => {
      const isLoggedIn = // get this from your state
    
      return (
        <NavigationContainer>
          {isLoggedIn ? <AppNavigator /> : <AuthNavigator />}
        </NavigationContainer>
      );
    };
    

    In your logout function, you would then reset to the ‘Login’ screen like this:

    navigation.reset({
      index: 0,
      routes: [{ name: 'Login' }],
    });
    

    This should correctly reset the navigation state and take you back to the login screen. Remember to replace createStackNavigator with whatever type of navigator you’re using (e.g., createStackNavigator, createBottomTabNavigator, etc.)

    Login or Signup to reply.
  2. Do not manually navigate when using conditionally rendering screens. Logout should just invalidate a state and login screen will automatically appear. Use Context to manage the state instead of passing props. Here is a super simple example:

    const AuthContext = createContext();
    
    function LoginScreen() {
      const { loggedIn, setLoggedIn } = useContext(AuthContext);
      return (
        <>
          <Button title="Login" onPress={() => setLoggedIn(true)} />
        </>
      );
    }
    
    function HomeScreen() {
      const { loggedIn, setLoggedIn } = useContext(AuthContext);
      return (
        <>
          <Button title="Logout" onPress={() => setLoggedIn(false)} />
        </>
      );
    }
    
    function App() {
      const [loggedIn, setLoggedIn] = useState(false);
      useEffect(() => {
        const getInitialState = async () => {
          const loggedIn = JSON.parse(await AsyncStorage.getItem('loggedIn'));
          setLoggedIn(loggedIn);
        };
        getInitialState();
      }, []);
    
      return (
        <AuthContext.Provider
          value={{
            loggedIn,
            setLoggedIn: (value) => {
              AsyncStorage.setItem('loggedIn', JSON.stringify(value));
              setLoggedIn(value);
            },
          }}>
          {loggedIn ? <HomeScreen /> : <LoginScreen />}
        </AuthContext.Provider>
      );
    }
    
    export default App;
    

    See Authentication flows for details.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search