I have a React Native app built with Expo and Expo Router. I’ve correctly configured autofill, and it works perfectly. On my Login screen, when I enter the username and password correctly, I get a successful response from my API and navigate to the next screen, and the iCloud Keychain popup to save my credentials appears as expected.
However, sometimes my API returns an error code when logging in, which means the user needs to enter a 2FA code. After entering the 2FA code, I press the login button again and navigate to the next screen successfully, but now the iCloud Keychain popup never appears.
Does anyone have any idea why this might be happening?
It seems like if the response from the API is not successful the iCloud popup does not appear anymore.
This issue occurs on both iOS and Android.
Here is my code:
import theme from '@config/theme';
import { router } from 'expo-router';
import React, { useState } from 'react';
import { Button, StyleSheet, Text, TextInput, View } from 'react-native';
import Toast from 'react-native-toast-message';
import { useSignIn } from '../hooks';
import { ErrorApiResponse, SignInForm } from '../types';
const SignInScreen = () => {
const { login } = useSignIn();
const [isOtpCodeRequired, setIsOtpCodeRequired] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [otpCode, setOtpCode] = useState('');
const handleOnSignIn = async ({ email, password, otpCode }: SignInForm) => {
try {
await login({
email,
password,
otpAttempt: otpCode,
});
router.navigate('/home');
} catch (error) {
const typedError = error as ErrorApiResponse;
if (
typedError?.response?.status === 401 &&
typedError?.response?.data?.errorCode === 12345
) {
setIsOtpCodeRequired(true);
} else {
Toast.show({
type: 'error',
text1: 'Login Error',
text2: typedError?.response?.data?.error,
});
}
}
};
return (
<View style={styles.mainContainer}>
<View style={styles.formContainer}>
<View style={{}}>
<Text>Email</Text>
<TextInput
value={email}
onChangeText={setEmail}
placeholder="Email"
keyboardType="email-address"
textContentType="emailAddress"
style={styles.input}
/>
</View>
<View style={{}}>
<Text>Password</Text>
<TextInput
value={password}
onChangeText={setPassword}
placeholder="Password"
secureTextEntry
textContentType="password"
style={styles.input}
/>
</View>
{isOtpCodeRequired && (
<View style={{}}>
<Text>Otp Code</Text>
<TextInput
placeholder="OTP CODE"
value={otpCode}
onChangeText={setOtpCode}
textContentType="oneTimeCode"
style={styles.input}
/>
</View>
)}
</View>
<View style={styles.buttonContainer}>
<Button
title="Log in"
onPress={() => handleOnSignIn({ email, password, otpCode })}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
justifyContent: 'space-between',
paddingHorizontal: theme.sizes.dimensions.padding * 2,
},
formContainer: { marginTop: 46 },
input: {
borderWidth: 1,
borderColor: theme.colors.gray500,
padding: 8,
borderRadius: 8,
},
buttonContainer: {
marginBottom: theme.sizes.dimensions.padding * 2,
},
});
export default SignInScreen;