Imran Irshad
Imran Irshad

Reputation: 97

ERROR The action 'NAVIGATE' with payload {...} was not handled by any navigator in React Native

I have implemented AWS custom UI authenctication in my react native app and React navigation to navigate through the different screens.

While implementing the logical conditions to see if the "User is already logged in or not" I have assigned the screen "Home" for logged in user and screen 'Login' for not logged in user it's working fine and navigating as expected but the console is showing this error when clicking on login button.

 ERROR  The action 'NAVIGATE' with payload {"name":"Home"} was not handled by any navigator.

Here is the Navigation code:

import React, {useEffect, useState} from 'react'
import {ActivityIndicator, Alert, View} from 'react-native';
import HomeScreen from '../screens/HomeScreen';
import LoginScreen from '../screens/LoginScreen';
import RegisterScreen from '../screens/RegisterScreen';
import ConfirmEmailScreen from '../screens/ConfirmEmailScreen';
import ForgotPassword from '../screens/ForgotPassword';
import NewPasswordScreen from '../screens/NewPasswordScreen';

import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import {Auth, Hub} from 'aws-amplify';

const Stack = createNativeStackNavigator();

const Navigation = () => {

  const [user, setUser] = useState(undefined);

  //Checking if user is already logged in or not!
  const checkUser = async () => {
    try {
      const authUser = await Auth.currentAuthenticatedUser({bypassCache: true});
      setUser(authUser);
    } catch(e) {
      setUser(null);
    }
  };

  useEffect(() => {
    checkUser();
  }, []);

  useEffect(() => {
    const listener = data => {
      if (data.payload.event === 'signIn' || data.payload.event === 'signOut') {
        checkUser();
      }
    }
    Hub.listen('auth', listener);
    return () => Hub.remove('auth', listener);
  }, []);

  if (user === undefined) {
    return (
      <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
        <ActivityIndicator/>
      </View>
    );
  } 

  return (
    <NavigationContainer>
      <Stack.Navigator>

        {/*{If user is logged in navigate him to Homescreen else go throght the Screens based on the user selection */}
        {user ? (
        <Stack.Screen name='Home' component={HomeScreen}/>
        ) : (
        <>
        <Stack.Screen name='Login' component={LoginScreen}/>
        <Stack.Screen name='Register' component={RegisterScreen}/>
        <Stack.Screen name='ConfirmEmail' component={ConfirmEmailScreen}/>
        <Stack.Screen name='ForgotPassword' component={ForgotPassword}/>
        <Stack.Screen name='NewPassword' component={NewPasswordScreen}/>
        </>
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default Navigation;

Here is the Login Screen:

import React, {useState} from 'react'
import { View, Text, Image, StyleSheet, useWindowDimensions, TouchableWithoutFeedback, Keyboard, Alert, KeyboardAvoidingView, ScrollView } from 'react-native'
import Logo  from '../../../assets/images/logo-main.png'
import CustomButton from '../../components/CustomButton/CustomButton';
import CustomInput from '../../components/CustomInput/CustomInput';
import { useNavigation } from '@react-navigation/native';

import {Auth} from 'aws-amplify';
import {useForm} from 'react-hook-form';

const LoginScreen = () => {

    const [loading, setLoading] = useState(false);
    const {height} = useWindowDimensions();
    const {control, handleSubmit, formState: {errors}} = useForm();
    const navigation = useNavigation();

    const onLoginPressed =  async (data) => {
        if(loading) {
            return;
        }
        setLoading(true);
        try {
            await Auth.signIn(data.username, data.password);
            navigation.navigate('Home');
        } catch(e) {
            Alert.alert('Opps', e.message)
        }
        setLoading(false);
    };

    const onForgotPasswordPressed = () => {
        navigation.navigate('ForgotPassword');
    }

    const onRegisterPressed = () => {
        navigation.navigate('Register')
    }

  return (
   
    <KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={styles.container}>
        <ScrollView contentContainerStyle={{flexGrow:1, justifyContent:'center'}} showsVerticalScrollIndicator={false}>
            <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
                <View style={styles.root}>
                    <Image source={Logo} style={[styles.logo, {height : height * 0.2}]} resizeMode={'contain'} />
        
                    <CustomInput icon='user' name='username' placeholder='Username' control={control} rules={{required: 'Username is required'}}  />
                    <CustomInput icon='lock' name='password' placeholder='Password' control={control} rules={{required: 'Password is required'}} secureTextEntry={true} />

                    <CustomButton text={loading ? 'Loading...' : 'Login Account'} onPress={handleSubmit(onLoginPressed)} />
                    <CustomButton text='Forgot Password?' onPress={onForgotPasswordPressed} type='TERTIARY' />
                    <CustomButton text="Don't have an account? Create one" onPress={onRegisterPressed} type='TERTIARY' />
                </View>
            </TouchableWithoutFeedback>
        </ScrollView>
    </KeyboardAvoidingView>
  );
};

const styles = StyleSheet.create({
    root: {
        alignItems: 'center',
        padding: 20,
    },

    logo: {
        width: 200,
        maxWidth: 300,
        maxHeight: 300,
    },
});

export default LoginScreen;

Upvotes: 1

Views: 4196

Answers (1)

Youssouf Oumar
Youssouf Oumar

Reputation: 45883

Issue

At the time you are calling navigation.navigate('Home') there is no screen called in Home in Navigation because of that ternary that checks whether there is a user or not and renders conditionally your screens. That's why React Naviagation is not happy.

Solution

React Navigation's documention tell us that we shouldn't "manually navigate when conditionally rendering screens​". Means you should find a way to call setUser(user) in Login where setUser is the state setter from Navigation component.

To do so we can use a context and for that change Navigation as follow (I added comments where I changed things):

import React, { createContext, useEffect, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import ConfirmEmailScreen from "../screens/ConfirmEmailScreen";
import ForgotPassword from "../screens/ForgotPassword";
import HomeScreen from "../screens/HomeScreen";
import LoginScreen from "../screens/LoginScreen";
import NewPasswordScreen from "../screens/NewPasswordScreen";
import RegisterScreen from "../screens/RegisterScreen";

import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

import { Auth, Hub } from "aws-amplify";
const Stack = createNativeStackNavigator();

// Line I added
export const AuthContext = createContext(null);

const Navigation = () => {
  const [user, setUser] = useState(undefined);

  //Checking if user is already logged in or not!
  const checkUser = async () => {
    try {
      const authUser = await Auth.currentAuthenticatedUser({ bypassCache: true });
      setUser(authUser);
    } catch (e) {
      setUser(null);
    }
  };

  useEffect(() => {
    checkUser();
  }, []);

  useEffect(() => {
    const listener = (data) => {
      if (data.payload.event === "signIn" || data.payload.event === "signOut") {
        checkUser();
      }
    };
    Hub.listen("auth", listener);
    return () => Hub.remove("auth", listener);
  }, []);

  if (user === undefined) {
    return (
      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
        <ActivityIndicator />
      </View>
    );
  }

  return (
     // Wrapper I added
    <AuthContext.Provider value={{ user, setUser }}>
      <NavigationContainer>
        <Stack.Navigator>
          {/*{If user is logged in navigate him to Homescreen else go throght the Screens based on the user selection */}
          {user ? (
            <Stack.Screen name="Home" component={HomeScreen} />
          ) : (
            <>
              <Stack.Screen name="Login" component={LoginScreen} />
              <Stack.Screen name="Register" component={RegisterScreen} />
              <Stack.Screen name="ConfirmEmail" component={ConfirmEmailScreen} />
              <Stack.Screen name="ForgotPassword" component={ForgotPassword} />
              <Stack.Screen name="NewPassword" component={NewPasswordScreen} />
            </>
          )}
        </Stack.Navigator>
      </NavigationContainer>
    </AuthContext.Provider>
  );
};

export default Navigation;

Consume AuthContext from Navigation in Login so you are able to call setUser after login, like below (I added comments where I changed thing):

import { useNavigation } from "@react-navigation/native";
import React, { useContext, useState } from "react";
import {
  Alert,
  Image,
  Keyboard,
  KeyboardAvoidingView,
  ScrollView,
  StyleSheet,
  TouchableWithoutFeedback,
  useWindowDimensions,
  View,
} from "react-native";
import Logo from "../../../assets/images/logo-main.png";
import CustomButton from "../../components/CustomButton/CustomButton";
import CustomInput from "../../components/CustomInput/CustomInput";

import { Auth } from "aws-amplify";
import { useForm } from "react-hook-form";

// Line I added
import {AuthContext} from "./Navigation" // ⚠️ use the correct path

const LoginScreen = () => {
  // Line I added
  const { setUser } = useContext(AuthContext); 

  const [loading, setLoading] = useState(false);
  const { height } = useWindowDimensions();
  const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm();
  const navigation = useNavigation();

  const onLoginPressed = async (data) => {
    if (loading) {
      return;
    }
    setLoading(true);
    try {
      const user = await Auth.signIn(data.username, data.password);
      // Line I added
      setUser(user);
    } catch (e) {
      Alert.alert("Opps", e.message);
    }
    setLoading(false);
  };

  const onForgotPasswordPressed = () => {
    navigation.navigate("ForgotPassword");
  };

  const onRegisterPressed = () => {
    navigation.navigate("Register");
  };

  return (
    <KeyboardAvoidingView
      behavior={Platform.OS === "ios" ? "padding" : "height"}
      style={styles.container}
    >
      <ScrollView
        contentContainerStyle={{ flexGrow: 1, justifyContent: "center" }}
        showsVerticalScrollIndicator={false}
      >
        <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
          <View style={styles.root}>
            <Image
              source={Logo}
              style={[styles.logo, { height: height * 0.2 }]}
              resizeMode={"contain"}
            />

            <CustomInput
              icon="user"
              name="username"
              placeholder="Username"
              control={control}
              rules={{ required: "Username is required" }}
            />
            <CustomInput
              icon="lock"
              name="password"
              placeholder="Password"
              control={control}
              rules={{ required: "Password is required" }}
              secureTextEntry={true}
            />

            <CustomButton
              text={loading ? "Loading..." : "Login Account"}
              onPress={handleSubmit(onLoginPressed)}
            />
            <CustomButton
              text="Forgot Password?"
              onPress={onForgotPasswordPressed}
              type="TERTIARY"
            />
            <CustomButton
              text="Don't have an account? Create one"
              onPress={onRegisterPressed}
              type="TERTIARY"
            />
          </View>
        </TouchableWithoutFeedback>
      </ScrollView>
    </KeyboardAvoidingView>
  );
};

const styles = StyleSheet.create({
  root: {
    alignItems: "center",
    padding: 20,
  },

  logo: {
    width: 200,
    maxWidth: 300,
    maxHeight: 300,
  },
});

Upvotes: 2

Related Questions