setState 不更新功能组件中的对象状态

时间:2021-05-15 18:32:06

标签: javascript react-native

我对 React Native 有点陌生,我遇到了一个有趣的情况。 我有一个登录屏幕,其中有两个字段;

  1. 手机号码。
  2. 密码

在登录按钮按下我正在执行两个功能:

  1. handleMobileInput() - 检查手机号码是否有效。 2 handlePasswordInput() - 将检查密码是否有效。

我还有一个名为“errors”的全局状态,最初它是空对象({})和“setErrors”函数来更新它。完整代码如下

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

现在,当我点击 login 时,handleLogin() 函数会执行并首先验证手机号码 因为手机号是空的(因为我没碰过,直接按登录),handleMobileInput()里面的变量x更新为{ mobError: 'mobile no is required },这是正确的。但是当我尝试将变量 x 设置为我的状态时,它不会更新它并返回空对象。但它确实正确更新了密码状态。现在我的最终状态“错误”看起来像这样

{
passError: 'password is required'
}

但我需要它

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

无论我按下登录按钮多少次,此状态都保持不变,并且 mobError 的状态永远不会更新。 所有这些功能都在我的主要功能组件中。 完整代码如下

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 个答案:

答案 0 :(得分:0)

setErrors 是异步的,不会立即显示更新的状态。

要检查更新 errors 状态,您应该将其记录在 useEffect 中。

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

要了解更多信息,您可以查看此link.

更新 在您的情况下,您同时调用 handleMobileInputhandlePasswordInput 并且它们都有 setErrors 它将异步更新状态,并且由于竞争条件而导致您遇到的问题.

因此,当您编写 setErrors({ ...errors, passError: 'password is required'}) 时,此时 errors 对象没有更新的 mobError 属性,因此该属性始终丢失。

为了克服这个问题,我建议您不要在这两个函数中都调用 setErrors,而是从它们返回一个 error 字符串。然后在您的 setErrors 方法中只调用一次 handleVote 。 请检查下面的代码。

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

答案 1 :(得分:0)

setStateasync 的方式工作。因此,您不会立即获得更新的值。

您可以为此使用 useEffect 并将状态作为依赖项传递,因此每次 errors 状态发生更改时,它都会被调用。

要实现您的目标,请使用 useEffect 钩子:

useEffect(() => console.log(errors), [errors])
相关问题