Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation passes for single character string; need to type two characters before getErrorsInField returns error messages #68

Open
kenny1983 opened this issue Sep 9, 2021 · 2 comments

Comments

@kenny1983
Copy link

Hi guys!

I have the following React context provider:

import React, { useState, useEffect, createContext } from 'react';

import auth from '@react-native-firebase/auth';
import { useValidation } from 'react-native-form-validator';

import { validationMsgs, validationRules } from './auth.validation';
import { ErrorText } from '../../features/auth/components/auth.styles';

export const AuthContext = createContext();

export const AuthContextProvider = ({ children }) => {
    const [ currentUser, setCurrentUser ] = useState(null);
    const [ passResetCode, setPassResetCode ] = useState('');

    const [ isLoading, setIsLoading ] = useState(false);
    const [ error, setError ] = useState(null);

    const [ displayName, setDisplayName ] = useState('');
    const [ emailAddress, setEmailAddress ] = useState('');

    const [ password, setPassword ] = useState('');
    const [ passwordMatch, setPasswordMatch ] = useState('');

    const { validate, getErrorsInField, isFormValid } =
        useValidation({
            state: {
                displayName,
                emailAddress,
                password,
                passwordMatch,
            },
            messages: validationMsgs,
        });

    const onLoginUser = async (email: string, pass: string) => {
        setIsLoading(true);

        try {
            await auth().signInWithEmailAndPassword(email, pass);
        } catch (e: any) {
            setError(e.toString());
        } finally {
            setIsLoading(false);
        }
    };

    // ...many other wrappers around various Firebase Auth functions, such as createUserWithEmailAndPassword

    const validateField = (field, numLines) => {
        const rules = {};
        rules[field] = validationRules[field];

        const isValid = validate(rules);

        return isValid ? null : getErrorsInField(field).map((e, i) => (
            <ErrorText key={i} numberOfLines={numLines}>
                {e.replace(/[A-Z]/g, letter => ` ${letter.toLowerCase()}`)
                    .replace(/^[a-z]/, letter => `${letter.toUpperCase()}`)}
            </ErrorText>
        ));
    };

    useEffect(() => {
        return auth().onAuthStateChanged(user => {
            if (user) {
                setCurrentUser(user);
            }
            setIsLoading(false);
        });
    }, []);

    return (
        <AuthContext.Provider
            value={{
                isAuthenticated: !!currentUser,
                currentUser,
                displayName,
                emailAddress,
                isLoading,
                error,
                password,
                passwordMatch,
                isFormValid,
                onLoginUser,
                onRegisterUser,
                onLogoutUser,
                onSendResetEmail,
                onResetPassword,
                setDisplayName,
                setEmailAddress,
                setError,
                setPassword,
                setPasswordMatch,
                validateField,
            }}>
            {children}
        </AuthContext.Provider>
    );
};

And the following consumer of this context:

import React, { useContext, useState } from 'react';
import { ScrollView } from 'react-native';
import { ActivityIndicator, Button, Colors, TextInput } from 'react-native-paper';

import { AuthContext } from '../../../services/auth/auth.context';
import { ErrorText } from '../components/auth.styles';
import { StyledText } from '../../../components/typography/text.component';

export const LoginScreen = ({ navigation }) => {
    const { emailAddress, error, isLoading, isFormValid, onLoginUser,
        password, setEmailAddress, setPassword, validateField,
    } = useContext(AuthContext);

    const [ fieldErrors, setFieldErrors ] = useState({});

    const onSubmit = () => {
        if (isFormValid()) {
            onLoginUser(emailAddress, password);
        }
    };

    return (
        <StyledText fontSize="h5" marginTop={'55%'} marginBottom="16px">
            Meals 2 U
        </StyledText>
        <ScrollView>
            <TextInput
                label="Email address"
                value={emailAddress}
                textContentType="emailAddress"
                keyboardType="email-address"
                autoCapitalize="none"
                onChangeText={e => {
                    setEmailAddress(e);
                    setFieldErrors({ ...fieldErrors, emailAddress:
                        validateField('emailAddress', 2) });
                }} />
            {fieldErrors.emailAddress}
            <TextInput
                label="Password"
                value={password}
                textContentType="password"
                secureTextEntry
                autoCapitalize="none"
                onChangeText={p => {
                    setPassword(p);
                    setFieldErrors({ ...fieldErrors, password:
                        validateField('password', 6) });
                }} />
            {fieldErrors.password}
            {error && (
                <ErrorText numberOfLines={3}>{error}</ErrorText>
            )}
            {isLoading ? (
                <ActivityIndicator
                    animating={true}
                    color={Colors.blue300}
                />
            ) : (
                <>
                    <Button
                        icon="lock-open-outline"
                        mode="contained"
                        onPress={() => onSubmit()}
                        disabled={!emailAddress || !password || !isFormValid()}>
                        Login
                    </Button>
                    <Button
                        icon="account-plus"
                        mode="contained"
                        onPress={() => {
                            setEmailAddress('');
                            setPassword('');
                            navigation.navigate('Register');
                        }}>
                        Register
                    </Button>
                    <Button
                        icon="lock-reset"
                        isLastButton={true}
                        mode="contained"
                        onPress={() => {
                            setEmailAddress('');
                            setPassword('');
                            navigation.navigate('Reset Password');
                        }}>
                        Reset Password
                    </Button>
                </>
            )}
        </ScrollView>
    );
};

This all works great, except that I have to enter two characters into either of the TextInputs before getErrorsInField actually returns any error messages.

Stepping through the code I can see that the validate function is returning false straight away (i.e. after typing a single character), but this has no effect on getErrorsInField until another character is typed.

Is this intended behaviour? If so, why?! It seems to me as though anyone would expect errors to be returned immediately!

In any case, how can I fix this behaviour? MTIA 😄.

@perscrew
Copy link
Owner

perscrew commented Sep 9, 2021

Hi, could we see the validationRulesobject please ?

@kenny1983
Copy link
Author

Sure! 😉

export const validationMsgs = {
    en: {
        numbers: '{0} must be a valid number.',
        email: '{0} must be a valid email address.',
        required: '{0} is a required field.',
        date: '{0} must be a valid date ({1}).',
        minlength: '{0} length must be greater than {1}.',
        maxlength: '{0} length must be lower than {1}.',
        equalPassword: 'both password fields must match.',
        hasUpperCase: '{0} must contain at least 1 uppercase letter.',
        hasLowerCase: '{0} must contain at least 1 lowercase letter.',
        hasNumber: '{0} must contain at least 1 digit.',
        hasSpecialCharacter: '{0} must contain at least 1 special character.',
    },
};

export const validationRules = {
    emailAddress: { email: true, required: true },
    password: {
        minlength: 12,
        hasNumber: true,
        hasUpperCase: true,
        hasLowerCase: true,
        hasSpecialCharacter: true,
        required: true,
    },
};

Thanks for the super fast response 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants