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;
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.
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,
},
});