Screen doesn't recognize Firebase auth errors - where is the problem? - javascript

I have a log in screen set up and I wanted to add alerts for when the email is invalid, for when the password is weak and for when they don't match. This is what I have:
const LoginScreen = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const incompleteForm = !email || !password
const [invalidEmail, setInvalidEmail] = useState(false);
const [wrongPassword, setWrongPassword] = useState(false);
const navigation = useNavigation();
const handleLogIn = () => {
auth.signInWithEmailAndPassword(email, password)
.then(userCredentials => {
const user = userCredentials.user;
navigation.replace('Navigation')
})
.catch((error) => {
if (error.code == "auth/weak-password") {
setTimeout(() => {
Alert.alert(
'Error',
'The password must be 6 characters long or more',
[{text: 'OK', onPress: () => navigation.navigate('Login')}],
{cancelable: false},
);
}, 100);
}
else if (error.code == "auth/invalid-email") {
setTimeout(() => {
Alert.alert(
'Error',
'The email address is not valid',
[{text: 'OK', onPress: () => navigation.navigate('Login')}],
{cancelable: false},
);
}, 100);
}
else if (error.code == "auth/wrong-password") {
setTimeout(() => {
Alert.alert(
'Error',
'Incorrect email or password',
[{text: 'OK', onPress: () => navigation.navigate('Login')}],
{cancelable: false},
);
}, 100);
}
})
}
return (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<KeyboardAvoidingView style = {styles.container} behavior = 'padding'>
<View style={styles.imageContainer}>
<Image source={require('../assets/pop1.png')} style={styles.image}/>
</View>
<View style = {styles.inputContainer}>
<TextInput
placeholder = 'Email'
value = {email}
onChangeText = {text => setEmail(text)}
autoCapitalize = 'none'
autoCorrect = {false}
/>
<TextInput
value = {password}
onChangeText = {text => setPassword(text)}
secureTextEntry
autoCapitalize = 'none'
autoCorrect = {false}
/>
</View>
<View>
<TouchableOpacity onPress = {handleLogIn}
disabled = {incompleteForm}>
<Text>Log in</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</TouchableWithoutFeedback>
)
}
export default LoginScreen
For some reason I only get the alert for the invalid username error and not for the other two and I can't figure it out since the same alert I have for the weak password works perfectly fine on another sign up screen I have. Where is the problem?
This is the working code on the sign up screen:
const LoginScreen = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const incompleteForm = !email || !password
const navigation = useNavigation();
const handleSignUp = async() => {
auth.createUserWithEmailAndPassword(email, password)
.then(userCredentials => {
const user = userCredentials.user;
navigation.navigate('Setup');
})
.catch((error) => {
if (error.code == "auth/email-already-in-use") {
setTimeout(() => {
Alert.alert(
'Error',
'The email address is already in use',
[{text: 'OK', onPress: () => navigation.navigate('Signup')}],
{cancelable: false},
);
}, 100);
}
else if (error.code == "auth/invalid-email") {
setTimeout(() => {
Alert.alert(
'Error',
'The email address is not valid',
[{text: 'OK', onPress: () => navigation.navigate('Signup')}],
{cancelable: false},
);
}, 100);
} else if (error.code == "auth/weak-password") {
setTimeout(() => {
Alert.alert(
'Error',
'The password must be 6 characters long or more',
[{text: 'OK', onPress: () => navigation.navigate('Signup')}],
{cancelable: false},
);
}, 100);
}
})
}
return (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<KeyboardAvoidingView behavior = 'padding'>
<StatusBar barStyle="light-content" translucent={true} />
<View style={styles.imageContainer}>
<Image source={require('../assets/pop.png')}/>
</View>
<View style = {styles.inputContainer}>
<TextInput
placeholder = 'Email'
value = {email}
onChangeText = {text => setEmail(text)}
autoCapitalize = 'none'
autoCorrect = {false}
/>
<TextInput
placeholder = 'Password'
value = {password}
onChangeText = {text => setPassword(text)}
secureTextEntry
autoCapitalize = 'none'
autoCorrect = {false}
/>
</View>
<View>
<TouchableOpacity onPress = {handleSignUp} disabled = {incompleteForm}>
<Text>Sign up</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</TouchableWithoutFeedback>
)
}
export default LoginScreen

signInWithEmailAndPassword doesn't generate the codes you're looking for, but createUserWithEmailAndPassword does. If the user already has an account that was created with the latter, Firebase doesn't need to check the password for weakness, and it doesn't need to check the email address for validity. The error Firebase needs to worry about with signing in is whether or not they got the password right, and it's not going to tell you if the email exists or not because that would be a security problem.
All that said, the code seems to be working fine. Just remove the error conditions that won't happen.

Related

todos not loading while using AsyncStorage

I am trying to use AsyncStorage to fetch my todos from inside the useEffect hook. If there are no todos(Meaning todos === []) Then a Text Component shows saying "Add a todo".
App image in expo
Initially the todos are set to "[]" inside the useState hook. When the addItem() method is called onPress the todos are not loading.
I do not know why this is happening...
export default function App() {
const [todo, setTodo] = useState('');
const [todos, setTodos] = useState([]);
useEffect(() => {
_retrieveData();
}, [todos]);
const addItem = (newTodo) => {
if (newTodo.length === 0) {
Alert.alert(
'Enter a String',
'You have entered a string with 0 characters',
[{ text: 'Okay', style: 'default' }]
);
} else {
console.log(newTodo);
let newTodos = [newTodo, ...todos];
setTodo('');
_storeData(JSON.stringify(newTodos));
}
};
const deleteTodo = (idx) => {
setTodos(todos.filter((todo, id) => id !== idx));
};
const _storeData = async (value) => {
try {
await AsyncStorage.setItem('TASKS', value);
} catch (error) {
// Error saving data
console.log(e);
}
};
const _retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
// We have data!!
setTodos(JSON.parse(value));
console.log(value);
}
} catch (error) {
// Error retrieving data
console.log(error);
}
};
return (
<TouchableWithoutFeedback
onPress={() => {
Keyboard.dismiss();
}}
>
<View style={styles.outerContainer}>
<Text style={styles.header}>TODO</Text>
<View style={styles.container}>
<TextInput
placeholder='new todo'
style={styles.input}
value={todo}
onChangeText={(text) => {
setTodo(text);
}}
></TextInput>
<Button title='Add' onPress={() => addItem(todo)}></Button>
</View>
<ScrollView style={styles.scrollView}>
{todos === [] ? (
<View>
<Text>Add a todo!</Text>
</View>
) : (
todos.map((todo, idx) => (
<View style={styles.todo} key={idx}>
<Text style={styles.todoText}>{todo}</Text>
<View style={styles.delete}>
<Button
color='red'
title='Delete'
onPress={() => deleteTodo(idx)}
></Button>
</View>
</View>
))
)}
</ScrollView>
</View>
</TouchableWithoutFeedback>
);
}
Dont use passed todo value newTodo, as setState is async dont get executed immediately, so you can use current setted todo value instead passed old value,
const addItem = (newTodo) => {
if (todo.length === 0) {
Alert.alert(
'Enter a String',
'You have entered a string with 0 characters',
[{ text: 'Okay', style: 'default' }]
);
} else {
console.log(todo);
let newTodos = [todo, ...todos];
setTodo('');
_storeData(JSON.stringify(newTodos));
setTodos(newTodos);
}
};

Why it takes two presses to do what i want? React Native

I have an input which passes its value to a useState on its parent.
That value gets passed to other component (Custom button).
There the input data gets validated and returns to the parent in another useState if there's an error and where ("e" = error in email, "p" = error in password, "ep" = error in email and password)
Then the border color of the input is set accordingly to that response, if there is an error it turns red, otherwise it turns white.
But it only works the second time i press the button (With which everything is supposed to start)
HELP!😣
const LoginScreen = () => {
const [email, setemail] = useState('');
const [password, setpassword] = useState('');
const [error, seterror] = useState('');
return (
<View style={styles.container}>
<Input
placeholder={"Correo :"}
setInputValue={value => setemail(value)}
color={
error.includes("e") ? '#FA8072' : 'white'
}
/>
<Input
placeholder={"Contraseña :"}
setInputValue={value => setpassword(value)}
color={
error.includes("p") ? '#FA8072' : 'white'
}
/>
<LoginButton data={{email: email, password: password}} setValue={value => {seterror(value)}}/>
</View>
)
}
=========================================
Input component
const Input = (props) => {
return (
<View style={styles.container}>
<Text style={styles.placeholder}>{props.placeholder}</Text>
<TextInput
style={[styles.input, {borderColor: props.color}]}
onChangeText={(value) => props.setInputValue(value)}
/>
</View>
)
}
=========================================
Button component
const LoginButton = (props) => {
const [inputError, setinputError] = useState('')
let validateData = () => {
if(!props.data.email && !props.data.password){
setinputError('ep')
}
else if(!props.data.email){
setinputError('e')
}
else if(!props.data.password){
setinputError('p')
}
else {
setinputError('')
}
}
return (
<TouchableOpacity style={styles.mainButton} onPress={() => {validateData(); props.setValue(inputError)}}>
<Text style={styles.mainButtonText}>Iniciar sesión</Text>
</TouchableOpacity>
)
}
Because you're trying to change state twice. Actually you don't need use state to pass value at LoginButton component. Try direct call instead.
const LoginButton = (props) => {
let validateData = () => {
if(!props.data.email && !props.data.password){
props.setValue("ep");
}
else if(!props.data.email){
props.setValue('e');
}
else if(!props.data.password){
props.setValue('p');
}
else {
props.setValue('');
}
}
return (
<TouchableOpacity style={styles.mainButton} onPress={() => validateData()}>
<Text style={styles.mainButtonText}>Iniciar sesión</Text>
</TouchableOpacity>
)
}

Render and return inside a loop

In my code, I call a function showUsers. This function displays all the users in a styled box. For now, the numberOfUsers passed into it is 1, as confirmed by console.logs as well.
So the loop should only runs once. However, all these console.logs are repeated twice. The output values remain the same. Why is this so?
console.log('Number of Users in Loop: ', numberOfUsers);
console.log('Whats the Id', userId);
console.log('UserName', userName);
console.log('i: ', i);
Code:
export const Page: React.FunctionComponent<PageProps> = ({
toggleShowPage,
showAddFriendEmailPage,
}) => {
const initialValues: FormValues = {
email: '',
};
const [errorMessage, setErrorMessage] = useState('');
const [userData, setUserData] = useState<UsersLazyQueryHookResult>('');
const [numberOfUsers, setNumberOfUsers] = useState('');
const validationSchema = emailValidationSchema;
useEffect(() => {
setUserData(userData);
setNumberOfUsers(numberOfUsers);
}, [userData, numberOfUsers]);
const showAlert = () => {
Alert.alert('Friend Added');
};
useEffect(() => {
if (showAddFriendEmailPage) return;
initialValues.email = '';
}, [showAddFriendEmailPage]);
const _onLoadUserError = React.useCallback((error: ApolloError) => {
setErrorMessage(error.message);
Alert.alert('Unable to Add Friend');
}, []);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
called: isMutationCalled,
},
] = useCreateUserRelationMutation({
onCompleted: (data: CreateUserRelationMutationResult) => {
showAlert();
},
});
const showUsers = React.useCallback(
(data: UsersLazyQueryHookResult, numberOfUsers: Number) => {
for (var i = 0; i < numberOfUsers; i++) {
console.log('Number of Users in Loop: ', numberOfUsers);
const userId = data.users.nodes[i].id;
const userName = ((data.users.nodes[i].firstName).concat(' ')).concat(data.users.nodes[i].lastName);
console.log('Whats the Id', userId);
console.log('UserName', userName);
console.log('Loop');
return(
<View style={styles.friends}>
<View style={styles.item}>
<Text>{userName}</Text>
</View>
</View>
)
}
},
[createUserRelationMutation],
);
const addFriend = React.useCallback(
(id: Number) => {
console.log('Whats the Id', id);
createUserRelationMutation({
variables: {
input: { relatedUserId: id, type: RelationType.Friend, userId: 7 },
},
});
},
[createUserRelationMutation],
);
const getFriendId = React.useCallback(
(data: UsersLazyQueryHookResult) => {
if (data) {
if (data.users.nodes.length == 0) {
setErrorMessage('User Not Found');
Alert.alert('User Not Found');
} else {
setUserData(data);
setNumberOfUsers(data.users.nodes.length);
//showUsers(data, Number(numberOfUsers));
addFriend(Number(data.users.nodes[0].id));
}
}
},
[addFriend],
);
const [loadUsers] = useUsersLazyQuery({
onCompleted: getFriendId,
onError: _onLoadUserError,
});
const handleSubmitForm = React.useCallback(
(values: FormValues, helpers: FormikHelpers<FormValues>) => {
console.log('Submitted');
loadUsers({
variables: {
where: { email: values.email },
},
});
values.email = '';
},
[loadUsers],
);
return (
<Modal
visible={showAddFriendEmailPage}
animationType="slide"
transparent={true}>
<SafeAreaView>
<View style={styles.container}>
<View style={styles.searchTopContainer}>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.searchFieldContainer}>
<View style={styles.form}>
<FieldInput
handleChange={handleChange}
handleBlur={handleBlur}
value={values.email}
fieldType="email"
/>
<ErrorMessage
name="email"
render={msg => (
<Text style={styles.errorText}>{msg}</Text>
)}
/>
</View>
<View style={styles.buttonContainer}>
<Button
rounded
style={styles.button}
onPress={handleSubmit}>
<Text style={styles.text}>Add Friend </Text>
</Button>
</View>
</View>
)}
</Formik>
</View>
{showUsers(userData, Number(numberOfUsers))}
</View>
</View>
</SafeAreaView>
</Modal>
);
};
UPDATED CODE:
export const Page: React.FunctionComponent<PageProps> = ({
toggleShowPage,
showAddFriendEmailPage,
}) => {
const initialValues: FormValues = {
email: '',
};
const [errorMessage, setErrorMessage] = useState('');
const [isSubmitted, setIsSubmitted] = useState(false);
const [userData, setUserData] = useState<UsersLazyQueryHookResult>('');
const [numberOfUsers, setNumberOfUsers] = useState('');
const validationSchema = emailValidationSchema;
const showUsers = React.useCallback(
(data: UsersLazyQueryHookResult, numberOfUsers: Number) => {
if (data){
for (var i = 0; i < numberOfUsers; i++) {
console.log('Number of Users in Loop: ', numberOfUsers);
const userId = data.users.nodes[i].id;
const userName = data.users.nodes[i].firstName
.concat(' ')
.concat(data.users.nodes[i].lastName);
return (
<View style={styles.friends}>
<View style={styles.item}>
<Text style={styles.userName}>{userName}</Text>
<View style={styles.addButtonContainer}>
<Button
onPress={() => {
addFriend(Number(data.users.nodes[i].id));
setIsSubmitted(false);
}}>
</Button>
</View>
</View>
</View>
);
}}
},
[createUserRelationMutation, userData, numberOfUsers],
);
const addFriend = React.useCallback(
(id: Number) => {
console.log('Whats the Id', id);
createUserRelationMutation({
variables: {
input: { relatedUserId: id, type: RelationType.Friend, userId: 7 },
},
});
},
[createUserRelationMutation],
);
const getFriendId = React.useCallback(
(data: UsersLazyQueryHookResult) => {
if (data) {
if (data.users.nodes.length == 0) {
setErrorMessage('User Not Found');
Alert.alert('User Not Found');
} else {
setUserData(data);
setNumberOfUsers(data.users.nodes.length);
}
}
},
[addFriend],
);
const [loadUsers] = useUsersLazyQuery({
onCompleted: getFriendId,
onError: _onLoadUserError,
});
const handleSubmitForm = React.useCallback(
(values: FormValues, helpers: FormikHelpers<FormValues>) => {
setIsSubmitted(true);
console.log('Submitted');
loadUsers({
variables: {
where: { email: values.email },
},
});
values.email = '';
},
[loadUsers],
);
if (!addingFriendLoading && isMutationCalled) {
if (addingFriendError) {
setErrorMessage(addingFriendError.message);
Alert.alert('Unable to Add Friend');
}
}
return (
<Modal
visible={showAddFriendEmailPage}
animationType="slide"
transparent={true}>
<SafeAreaView>
<View style={styles.container}>
<View style={styles.searchTopContainer}>
<View style={styles.searchTopTextContainer}>
<Text
style={styles.searchCancelDoneText}
onPress={() => {
toggleShowPage()
setIsSubmitted(false);
setUserData(null);
}}
>
Cancel
</Text>
<Text style={styles.searchTopMiddleText}>
Add Friend by Email
</Text>
<Text style={styles.searchCancelDoneText}>Done</Text>
</View>
<View>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.searchFieldContainer}>
<View style={styles.form}>
<FieldInput
handleChange={handleChange}
handleBlur={handleBlur}
value={values.email}
fieldType="email"
/>
<ErrorMessage
name="email"
render={msg => (
<Text style={styles.errorText}>{msg}</Text>
)}
/>
</View>
<View style={styles.buttonContainer}>
<Button
rounded
style={styles.button}
onPress={handleSubmit}>
<Text style={styles.text}>Search </Text>
</Button>
</View>
</View>
)}
</Formik>
</View>
{isSubmitted && showUsers(userData, Number(numberOfUsers))}
</View>
</View>
</SafeAreaView>
</Modal>
);
};
You are also calling setUserData from getFriendId, so it will update userData
const getFriendId = React.useCallback(
....
setUserData(data);
So it will invoke render and useEffect
useEffect(() => {
setUserData(userData);
setNumberOfUsers(numberOfUsers);
}, [userData, numberOfUsers]);
You can comment out useEffect and check no of console.log

Unable to Type in <Formik> Field

interface FormValues {
friendEmail: string;
}
const initialValues: FormValues = {
friendEmail: '',
};
export const Page: React.FunctionComponent<PageProps> = ({
toggleShowPage,
showPage,
}) => {
const [errorMessage, setErrorMessage] = useState('');
const validationSchema = emailValidationSchema;
useEffect(() => {
if (showPage) return;
initialValues.friendEmail = '';
}, [showPage]);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
called: isMutationCalled,
},
] = useCreateUserRelationMutation({
onCompleted: (data: any) => {
showAlert();
},
});
const addFriend = React.useCallback(
(id: Number) => {
console.log('Whats the Id', id);
createUserRelationMutation({
variables: {
input: { relatedUserId: id, type: RelationType.Friend, userId: 7 },
},
});
},
[createUserRelationMutation],
);
const getFriendId = React.useCallback(
(data: any) => {
//console.log('Email', initialValues.friendEmail);
if (data) {
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
Alert.alert('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
addFriend(Number(data.users.nodes[0].id));
}
}
},
[addFriend],
//[friendEmail, addFriend],
);
const [loadUsers] = useUsersLazyQuery({
onCompleted: getFriendId,
onError: _onLoadUserError,
});
const handleSubmitForm = React.useCallback(
(values: FormValues, helpers: FormikHelpers<FormValues>) => {
console.log('Submitted');
loadUsers({
variables: {
where: { email: values.friendEmail },
},
});
//setFriendEmail('');
values.friendEmail = '';
},
[loadUsers],
//[loadUsers, friendEmail]
);
if (!addingFriendLoading && isMutationCalled) {
if (addingFriendData) {
console.log('Checking');
}
if (addingFriendError) {
console.log('errorFriend', addingFriendError.message);
}
}
return (
<Modal
visible={showAddFriendEmailPage}
animationType="slide"
transparent={true}>
<SafeAreaView>
<View style={scaledAddFriendEmailStyles.container}>
<View style={scaledAddFriendEmailStyles.searchTopContainer}>
<View style={scaledAddFriendEmailStyles.searchTopTextContainer}>
<Text
style={scaledAddFriendEmailStyles.searchCancelDoneText}
onPress={toggleShowPage}>
Cancel
</Text>
<Text style={scaledAddFriendEmailStyles.searchTopMiddleText}>
Add Friend by Email
</Text>
<Text style={scaledAddFriendEmailStyles.searchCancelDoneText}>
Done
</Text>
</View>
<View style={scaledAddFriendEmailStyles.searchFieldContainer}>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
values,
}) => (
<View>
<View>
<Item style={scaledAddFriendEmailStyles.searchField}>
<TextInput
style={scaledAddFriendEmailStyles.searchText}
placeholder="Email"
onChangeText={handleChange('friendEmail')}
//onChangeText={e => console.log('Workinggg')}
onBlur={handleBlur('friendEmail')}
value={values.friendEmail}
autoCapitalize="none"
/>
{/* <Field
component={Input}
name='friendEmail'
placeholder="Email"
//handleChange={handleChange}
handleBlur={handleBlur}
//onChange={handleChange}
//onChangeText={handleChange('friendEmail')}
//onBlur={handleBlur('friendEmail')}
value={values.friendEmail}
autoCapitalize="none"
/> */}
</Item>
</View>
<View>
<Button
onPress={handleSubmit}>
<Text>
Add Friend{' '}
</Text>
</Button>
</View>
</View>
)}
</Formik>
</View>
</View>
</View>
</SafeAreaView>
</Modal>
);
};
Why am I unable to write anything inside my Input field? I have tried using onChangeand handleChangeboth but it doesn't make a difference. Other SO answers suggested that I should remove value but examples of Formik usage that I see online suggest otherwise.
I am trying to follow this for my Formik validation:
https://heartbeat.fritz.ai/build-and-validate-forms-in-react-native-using-formik-and-yup-6489e2dff6a2
EDIT:
I also tried with setFieldValuebut I still cannot type anything.
const initialValues: FormValues = {
friendEmail: '',
};
export const AddFriendEmailPage: React.FunctionComponent<AddFriendEmailPageProps> = ({
toggleShowPage,
showAddFriendEmailPage,
}) => {
const [errorMessage, setErrorMessage] = useState('');
const validationSchema = emailValidationSchema;
useEffect(() => {
if (showAddFriendEmailPage) return;
initialValues.friendEmail = '';
}, [showAddFriendEmailPage]);
const _onLoadUserError = React.useCallback((error: ApolloError) => {
setErrorMessage(error.message);
Alert.alert('Unable to Add Friend');
}, []);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
called: isMutationCalled,
},
] = useCreateUserRelationMutation({
onCompleted: (data: any) => {
showAlert();
},
});
const addFriend = React.useCallback(
(id: Number) => {
console.log('Whats the Id', id);
createUserRelationMutation({
variables: {
input: { relatedUserId: id, type: RelationType.Friend, userId: 7 },
},
});
},
[createUserRelationMutation],
);
const getFriendId = React.useCallback(
(data: any) => {
//console.log('Email', initialValues.friendEmail);
if (data) {
if (data.users.nodes.length == 0) {
console.log('No user');
} else {
console.log('ID', data.users.nodes[0].id);
addFriend(Number(data.users.nodes[0].id));
}
}
},
[addFriend],
//[friendEmail, addFriend],
);
const [loadUsers] = useUsersLazyQuery({
onCompleted: getFriendId,
onError: _onLoadUserError,
});
const handleSubmitForm = React.useCallback(
(values: FormValues, helpers: FormikHelpers<FormValues>) => {
console.log('Submitted');
loadUsers({
variables: {
where: { email: values.friendEmail },
},
});
//setFriendEmail('');
values.friendEmail = '';
},
[loadUsers],
//[loadUsers, friendEmail]
);
return (
<Modal
visible={showPage}
animationType="slide"
transparent={true}>
<SafeAreaView>
<View style={scaledAddFriendEmailStyles.container}>
<View style={scaledAddFriendEmailStyles.searchFieldContainer}>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({
handleChange,
setFieldValue,
handleBlur,
handleSubmit,
isSubmitting,
values,
}) => {
const setEmail = (friendEmail: string) => {
setFieldValue('friendEmail', friendEmail)
}
return(
<View>
<View>
<Item>
<TextInput
placeholder="Email"
onChangeText={setEmail}
onBlur={handleBlur('friendEmail')}
value={values.friendEmail}
autoCapitalize="none"
/>
</Item>
</View>
<View >
<Button
onPress={handleSubmit}>
<Text >
Add Friend{' '}
</Text>
</Button>
</View>
</View>
)}}
</Formik>
</View>
</View>
</View>
</SafeAreaView>
</Modal>
);
};
Formik's Field component doesn't support React native yet. Check this github issue for more details
However you can make use of TextInput in place of field and use it with onChangeText handler
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
values,
}) => (
<View>
<View>
<Item style={scaledAddFriendEmailStyles.searchField}>
<TextInput
placeholder="Email"
onChangeText={handleChange('friendEmail')}
onBlur={handleBlur('friendEmail')}
value={values.friendEmail}
/>
</Item>
</View>
<View >
<Button
onPress={handleSubmit}
>
<Text >
Add Friend{' '}
</Text>
</Button>
</View>
</View>
)}
</Formik>
you can read more about Formik's usage with react-native in its documentation here
try this:
<Input
placeholder="Email"
onChange={e => setFieldValue('friendEmail', e.currentTarget.value)}
onBlur={handleBlur}
value={values.friendEmail}
autoCapitalize="none"
/>
I think there are a couple of issues in your codebase.
onChangeText={handleChange('friendEmail')}. It will trigger the handleChange while rendering the component not when you are actualy typing in the input box.
handleChange function of Formik takes React.ChangeEvent instead of value. Check here . While onChangeText of react-native provides changed text of the input not event. Check here
You can use setFieldValue function for this case:
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
values,
setFieldValue
}) => {
const setEmail = (email) => {
setFieldValue('friendEmail', email)
}
return (
<View>
<View>
<Item style={scaledAddFriendEmailStyles.searchField}>
<TextInput
placeholder="Email"
onChangeText={setEmail}
value={values.friendEmail}
/>
</Item>
</View>
</View>
)
}}
</Formik>
Please Note: I've never used formik with react-native. Just trying to connect the dots.
Formik now works fine with React Native, but another thing to be aware of is that the name in your form control must match the name of the property in the schema used by Formik.
For example, if using the following Yup schema:
const schema = yup.object({
personName: yup.string().required('Name required')
});
Then the following won't work because the Yup schema (personName) and the form control (nameofPerson) don't match; the user won't be able to type into the field:
<Form.Control
name="nameOfPerson"
value={values.personName}
onChange={handleChange}
onBlur={handleBlur}
type="text"
isValid={!errors.personName && !!touched.personName}
isInvalid={!!errors.personName && !!touched.personName}
/>
To make it work, the name should be the same as the Yup property; in this case, personName:
<Form.Control
name="personName"
value={values.personName}
onChange={handleChange}
onBlur={handleBlur}
type="text"
isValid={!errors.personName && !!touched.personName}
isInvalid={!!errors.personName && !!touched.personName}
/>
This example is using React-Bootstrap Form.Control but should apply to any manner of creating form controls.

Why the state of a useState snaps back to false if I go back and forth on the Screen?

I've been trying to toggle favorite products and store their id and an isFav prop on a Firebase database. Then I use them to show the favorites on the FavoritesScreen.
If I go to the ProductDetailsScreen (where I toggle the favorite) I toggle it true/false with no problem.
Further if I then use the Bottom Tab Navigation to check the FavoritesScreen or the OrdersScreen etc and then go back to ProductDetailsScreen, nothing is changed.
But if (from the ProductDetailsScreen) I go back (to ProductsOverviewScreen) and then come back again on ProductDetailsScreen the state of isFav
snaps back to false! Nevertheless the id and isFav are saved on Firebase, but isFav is saved as false.
Note: I use a useState() hook...
One more thing that I don't understand happens when I try to log isFav.
I have two logs, one inside the toggleFavoriteHandler and one outside. When I first run the toggleFavoriteHandler, where I also have setIsFav(prevState => !prevState); I get:
Output:
outside: false
inside: false
outside: true
So I guess the first two false are from the initial state and then the true is from the above state-toggling. But why it gets it only outside true? Why actually the first two are false? I change the state to true before the log. I would expect it to immediately change to true and have them all true!
Then if I go back to ProductsOverviewScreen and then again to ProductDetailsScreen I get two logs from outside:
Output:
outside: true
outside: false
So it snaps back to its initial state! ?
I really do not understand how the work-flow goes. Are these logs normal?
Can anybody give some hints where the bug from going back and forth could be, please?
Thanks!
Here is the code:
ProductDetailsScreen.js
...
const ProductDetailScreen = (props) => {
const [ isFav, setIsFav ] = useState(false);
const dispatch = useDispatch();
const productId = props.navigation.getParam('productId');
const selectedProduct = useSelector((state) =>
state.products.availableProducts.find((prod) => prod.id === productId)
);
const toggleFavoriteHandler = useCallback(
async () => {
setError(null);
setIsFav((prevState) => !prevState);
console.log('isFav inside:', isFav); // On first click I get: false
try {
await dispatch(
productsActions.toggleFavorite(
productId,
isFav,
)
);
} catch (err) {
setError(err.message);
}
},
[ dispatch, productId, isFav setIsFav ]
);
console.log('isFav outside: ', isFav); // On first click I get: false true
return (
<ScrollView>
<View style={styles.icon}>
<TouchableOpacity style={styles.itemData} onPress={toggleFavoriteHandler}>
<MaterialIcons name={isFav ? 'favorite' : 'favorite-border'} size={23} color="red" />
</TouchableOpacity>
</View>
<Image style={styles.image} source={{ uri: selectedProduct.imageUrl }} />
{Platform.OS === 'android' ? (
<View style={styles.button}>
<CustomButton
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(selectedProduct))}
/>
</View>
) : (
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(selectedProduct))}
/>
</View>
)}
<Text style={styles.price}>€ {selectedProduct.price.toFixed(2)}</Text>
<Text style={styles.description}>{selectedProduct.description}</Text>
</ScrollView>
);
};
ProductDetailScreen.navigationOptions = ({ navigation }) => {
return {
headerTitle: navigation.getParam('productTitle'),
headerLeft: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="goBack"
iconName={Platform.OS === 'android' ? 'md-arrow-back' : 'ios-arrow-back'}
onPress={() => navigation.goBack()}
/>
</HeaderButtons>
),
headerRight: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="cart"
iconName={Platform.OS === 'android' ? 'md-cart' : 'ios-cart'}
onPress={() => navigation.navigate({ routeName: 'Cart' })}
/>
</HeaderButtons>
)
};
};
...styles
products.js/actions
export const toggleFavorite = (id, isFav) => {
return async (dispatch) => {
try {
// If it is a favorite, post it.
// Note it is initially false...
if (!isFav) {
const response = await fetch('https://ekthesi-7767c.firebaseio.com/favorites.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id,
isFav
})
});
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
const resData = await response.json();
// Note: No `name` property, that's why we use a `for_in` loop
// console.log('POST', JSON.stringify(resData));
dispatch({ type: TOGGLE_FAVORITE, productId: id });
} else if (isFav) {
// First get the key in order to delete it in second fetch(...).
const response = await fetch(`https://ekthesi-7767c.firebaseio.com/favorites.json`);
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
const resData = await response.json();
// Note: No `name` property, that's why we use a `for_in` loop
// console.log('fetch', JSON.stringify(resData));
for (const key in resData) {
console.log('resData[key].id', resData[key].id === id);
if (resData[key].id === id) {
await fetch(`https:app.firebaseio.com/favorites/${key}.json`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
// console.log('fetch', JSON.stringify(resData));
dispatch({ type: TOGGLE_FAVORITE, productId: id });
}
}
}
} catch (err) {
// send to custom analytics server
throw err;
}
};
};
ProductsOverviewScreen.js
...
const ProductsOverviewScreen = (props) => {
const [ isLoading, setIsLoading ] = useState(false);
const [ error, setError ] = useState(); // error initially is undefined!
const [ isRefresing, setIsRefresing ] = useState(false);
const dispatch = useDispatch();
const categoryId = props.navigation.getParam('categoryId');
const products = useSelector((state) =>
state.products.availableProducts.filter((prod) => prod.categoryIds.indexOf(categoryId) >= 0)
);
const productId = props.navigation.getParam('productId');
const isFav = useSelector((state) => state.products.favoriteProducts.some((product) => product.id === productId));
const loadProducts = useCallback(
async () => {
setError(null);
setIsRefresing(true);
try {
await dispatch(productsActions.fetchProducts());
} catch (err) {
setError(err.message);
}
setIsRefresing(false);
},
[ dispatch, setIsLoading, setError ]
);
// loadProducts after focusing
useEffect(
() => {
const willFocusEvent = props.navigation.addListener('willFocus', loadProducts);
return () => willFocusEvent.remove();
},
[ loadProducts ]
);
// loadProducts initially...
useEffect(
() => {
setIsLoading(true);
loadProducts();
setIsLoading(false);
},
[ dispatch, loadProducts ]
);
const selectItemHandler = (id, title) => {
props.navigation.navigate('DetailScreen', {
productId: id,
productTitle: title,
isFav: isFav
});
};
if (error) {
return (
<View style={styles.centered}>
<Text>Something went wrong!</Text>
<Button title="Try again" onPress={loadProducts} color={Colours.chocolate} />
</View>
);
}
if (isLoading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colours.chocolate} />
</View>
);
}
if (!isLoading && products.length === 0) {
return (
<View style={styles.centered}>
<Text>No products yet!</Text>
</View>
);
}
return (
<FlatList
onRefresh={loadProducts}
refreshing={isRefresing}
data={products}
keyExtractor={(item) => item.id}
renderItem={(itemData) => (
<ProductItem
title={itemData.item.title}
image={itemData.item.imageUrl}
onSelect={() => selectItemHandler(itemData.item.id, itemData.item.title)}
>
{Platform.OS === 'android' ? (
<View style={styles.actions}>
<View>
<CustomButton
title="Details"
onPress={() => selectItemHandler(itemData.item.id, itemData.item.title)}
/>
</View>
<BoldText style={styles.price}>€ {itemData.item.price.toFixed(2)}</BoldText>
<View>
<CustomButton
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(itemData.item))}
/>
</View>
</View>
) : (
<View style={styles.actions}>
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Details"
onPress={() => selectItemHandler(itemData.item.id, itemData.item.title)}
/>
</View>
<BoldText style={styles.price}>€ {itemData.item.price.toFixed(2)}</BoldText>
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(itemData.item))}
/>
</View>
</View>
)}
</ProductItem>
)}
/>
);
};
ProductsOverviewScreen.navigationOptions = (navData) => {
return {
headerTitle: navData.navigation.getParam('categoryTitle'),
headerRight: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="cart"
iconName={Platform.OS === 'android' ? 'md-cart' : 'ios-cart'}
onPress={() => navData.navigation.navigate({ routeName: 'Cart' })}
/>
</HeaderButtons>
)
};
};
...styles
State updates are not synchronous. Considering the following:
const [isFav, setIsFav] = React.useState(true);
setIsFav(false); // state update here
console.log(isFav); // isFav hasn't updated yet and won't be `false` until next render
To get the latest state, you need to put your log in useEffect/useLayoutEffect.
From React docs,
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may batch or defer the update until later.
https://reactjs.org/docs/react-component.html#setstate
After the comment of #satya I gave it another try.
Now I get the state of isFav from the redux state.
Namely, I check if the current product is in the favoriteProducts array.
...imports
const ProductDetailScreen = (props) => {
const [ error, setError ] = useState(); // error initially is undefined!
const dispatch = useDispatch();
const productId = props.navigation.getParam('productId');
const selectedProduct = useSelector((state) =>
state.products.availableProducts.find((prod) => prod.id === productId)
);
// HERE !!! I get to see if current product is favorite!
const currentProductIsFavorite = useSelector((state) => state.products.favoriteProducts.some((product) => product.id === productId));
const toggleFavoriteHandler = useCallback(
async () => {
setError(null);
try {
await dispatch(productsActions.toggleFavorite(productId, currentProductIsFavorite));
} catch (err) {
setError(err.message);
}
},
[ dispatch, productId, currentProductIsFavorite, setIsFav ]
);
...
return (
<ScrollView>
<View style={styles.icon}>
<TouchableOpacity style={styles.itemData} onPress={toggleFavoriteHandler}>
<MaterialIcons name={currentProductIsFavorite ? 'favorite' : 'favorite-border'} size={23} color="red" />
</TouchableOpacity>
</View>
<Image style={styles.image} source={{ uri: selectedProduct.imageUrl }} />
...

Categories

Resources