1

I am a bit new in react native and I have got into an interesting situation. I have a Login screen in which there are two fields;

  1. Mobile number.
  2. password

On Login button press I am executing two functions :

  1. handleMobileInput() - which check if the mobile number is valid. 2 handlePasswordInput() - which will check if password is valid.

I also have a global state named 'errors' initially it is empty object({}) and 'setErrors' function to update it. Full code looks like this

    const [errors, setErrors] = useState({});
    const handleLogin = () => {
        handleMobileInput(mobileNo);
        handlePasswordInput(password);
        if (errors.mobError || errors.passError) {
            return;
        }
    }
    const handleMobileInput = (mobileNo) => {
        if (!mobileNo) {
            const x = { ...errors, mobError: 'mobile no. is required'}
            console.log(x); //outputs correctly
            setErrors(x);
            console.log(errors); //output incorrectly even when I click login again and again
        } else if (mobileNo.length !== 10) {
            setErrors({ ...errors, mobError: 'mobile no must be of 10 digits'});
        } else {
            setErrors({ ...errors, mobError: ''});
        }
        setMobileNo(mobileNo);
    }

    const handlePasswordInput = (password) => {
        if (!password) {
            setErrors({ ...errors, passError: 'password is required'})
        } else if (password.length < 5) {
            setErrors({ ...errors, passError: 'password must be 6 characters long'});
        } else {
            setErrors({ ...errors, passError: ''})
        }
        setPassword(password);
    }

Now when I click login , handleLogin() function executes and it first validates mobile Number since mobile number is empty(because I haven't touched it, I presses Login directly), variable x inside handleMobileInput() updated as { mobError: 'mobile no is required } which is correct. But when I try to set the variable x to my state, it doesn't update it and return empty object. But it does update password state correctly. Now my final state 'errors' looks like this

{
passError: 'password is required'
}

but I need it as

{
mobError: 'mobile no is required',
passError: 'pass is required'
}

No matter How many times I presses the login button this state remains same and state for mobError never gets updated. All these functions are inside my main functional component. Full code looks like this

import React  from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, CheckBox } from 'react-native';
import { Button, Card, Title, Divider } from 'react-native-paper';
import { AntDesign } from '@expo/vector-icons'; 
import { default as globalStyles }  from '../globalStyles';
import { useState } from 'react';


const LoginScreen = ({ navigation }) => {
    const [errors, setErrors] = useState({});
    const [mobileNo, setMobileNo] = useState('');
    const [password, setPassword] = useState('');

    const handleMobileInput = (mobileNo) => {
        if (!mobileNo) {
            const x = { ...errors, mobError: 'mobile no. is required'}
            console.log(x); //outputs correctly
            setErrors(x);
            console.log(errors); //output incorrectly even when I click login again and again
        } else if (mobileNo.length !== 10) {
            setErrors({ ...errors, mobError: 'mobile no must be of 10 digits'});
        } else {
            setErrors({ ...errors, mobError: ''});
        }
        setMobileNo(mobileNo);
    }

    const handlePasswordInput = (password) => {
        if (!password) {
            setErrors({ ...errors, passError: 'password is required'})
        } else if (password.length < 5) {
            setErrors({ ...errors, passError: 'password must be 6 characters long'});
        } else {
            setErrors({ ...errors, passError: ''})
        }
        setPassword(password);
    }

    const handleLogin = () => {
        handleMobileInput(mobileNo);
        handlePasswordInput(password);
        if (errors.mobError || errors.passError) {
            return;
        }
    }

    return (
        <View  style={globalStyles.viewStyle}>
            <Card style={globalStyles.cardStyle}>
              <Card.Content>
                <Title style={globalStyles.title}>Welcome to Mshur</Title>
                <Divider style={{...globalStyles.divider, ...globalStyles.bgColorPrimary}}></Divider>
              </Card.Content>
              <View style={globalStyles.inputView}>
                <AntDesign name="user" size={24} color="white" style={{...globalStyles.inputIconStyle, ...globalStyles.bgColorPrimary}}/>
                <TextInput 
                    style={globalStyles.inputStyle} 
                    placeholder="enter mobile number" 
                    placeholderTextColor="grey"
                    keyboardType="numeric"
                    onChange = {(e) => {
                        handleMobileInput(e.nativeEvent.text);
                    }}>
                </TextInput>
                
              </View>
              {
                    errors.mobError ? 
                        (<Text style={globalStyles.error}>{errors.mobError}</Text>)
                    : null

              }
              <View style={{...globalStyles.inputView, ...globalStyles.marginTop_1}}>
                <AntDesign name="lock" size={24} color="white" style={{...globalStyles.inputIconStyle, ...globalStyles.bgColorPrimary}}/>
                <TextInput 
                    style={globalStyles.inputStyle} 
                    placeholder="enter password" 
                    placeholderTextColor="grey"
                    onChange = {(e) => {
                        handlePasswordInput(e.nativeEvent.text);
                    }}
                    >
                </TextInput>
              </View>
              {
                    errors.passError ? 
                        (<Text style={globalStyles.error}>{errors.passError}</Text>)
                    : null

              }
              <View style={styles.loginHelp}>
                  <View style={globalStyles.checkboxContainer}>
                    <CheckBox value={true}></CheckBox>
                    <Text style={{fontSize: 13}}>Remember me?</Text>
                  </View>

                  <TouchableOpacity style={globalStyles.TouchableOpacityStyle}>
                      <Text style={{color: 'grey'}}>Forgot Password</Text>
                  </TouchableOpacity>
              </View>
              <Button 
                style={{marginHorizontal: 15, ...globalStyles.bgColorPrimary, ...globalStyles.buttonStyle}} 
                mode="contained" 
                contentStyle = {{ height: 40 }} 
                onPress={() => handleLogin()}>
                Login 
              </Button>
              <Button 
                style={{marginHorizontal: 15, ...globalStyles.bgColorSeconday, ...globalStyles.marginTop_1, ...globalStyles.buttonStyle}} 
                mode="contained" 
                contentStyle = {{ height: 40 }} 
                onPress={() => navigation.navigate('SignUp')}>
                New user? sign up 
              </Button>
            </Card>
        </View>
        
        
    )
}

const styles = StyleSheet.create({
    loginHelp: {
        display:'flex',
        flexDirection: 'row',
        justifyContent: 'space-between',
        marginHorizontal: 15,
    },
});

export default LoginScreen;

2 Answers2

0

setErrors is asynchronous and would not show you updated state right away.

To check the update errors state you should log it inside a useEffect.

useEffect(()=>{
   console.log(errors) // will execute everytime errors change
},[errors])

To learn more you can check this link.

UPDATE In your case you are calling handleMobileInput and handlePasswordInput at the same time and both of them have setErrors which will update the state asynchronously and the problem that you are getting is happening because of race conditions.

So when you write setErrors({ ...errors, passError: 'password is required'}) , at this time errors object doesn't have the updated mobError property and hence that property is always missing.

In order to overcome that I suggest that u don't call setErrors in both these functions but instead return a error string from them. and then call setErrors only once , in your handleVote method. Please check the code below.

const handleMobileInput = (mobileNo) => {
   let mobileError;
  if (!mobileNo) {
    mobileError = 'mobile no. is required'
  } else if (mobileNo.length !== 10) {
    mobileError = 'mobile no must be of 10 digits';
  } else {
    mobileError = ''
     
  }
  setMobileNo(mobileNo);
  return mobileError;
}

const handlePasswordInput = (password) => {
  let passwordError;
  if (!password) {
    passwordError = 'password is required'
  } else if (password.length < 5) {
    passwordError = 'password is required'
  } else {
    passwordError = 'password is required'
  }
  setPassword(password);
  return passwordError;
}

const handleLogin = () => {
  const mobError = handleMobileInput(mobileNo);
  const passError = handlePasswordInput(password);
  setErrors({...errors,passError,mobError})
  if (mobError || passError) {
      return;
  }
}

useEffect(()=>{console.log(errors},[errors]) // will have updated fields
Cybershadow
  • 1,097
  • 9
  • 16
  • Thanks for your suggestion. I don't think asynchronousity is the problem here, I have tried with ```await setState(errors)``` but the same issue, and the problem is not with console logging actually the error state never gets updated here. useEffect also didn't made any effect. – Deepak Yadav May 16 '21 at 06:51
  • Actually `setState` is not a promise so adding `await` won't make any difference.It just update state asynchronously and when state gets updated the callback function in `useEffect` will get fired. Do you get anything in the console with `useEffect`? – Cybershadow May 16 '21 at 11:39
  • Yes I got the object which I was previously getting (problem is not yet solved). The object I got is ``` { "passError": "password is required"}. ``` – Deepak Yadav May 16 '21 at 11:55
  • What I am expecting is ``` { "mobError": "mobile is required", passError: " password is required" } – Deepak Yadav May 16 '21 at 11:58
  • Thanks for explanation. setState is very confusing – Deepak Yadav May 16 '21 at 15:25
0

setState works async way. As a result, you are not getting the updated value immediately.

You can use useEffect for that and pass the state as dependency, so every time errors state get change it would get invoke.

To achieve your goal use useEffect hook:

useEffect(() => console.log(errors), [errors])
Rashed Rahat
  • 2,357
  • 2
  • 18
  • 38
  • Thanks for your suggestion. I don't think asynchronousity is the problem here, I have tried with ```await setState(errors)``` but the same issue, and the problem is not with console logging actually the error state never gets updated here. useEffect also didn't made any effect. – Deepak Yadav May 16 '21 at 06:51