React native не выполняет повторную визуализацию со всеми новыми состояниями

#reactjs #typescript #jsx #native

#reactjs #typescript #jsx #native

Вопрос:

я создаю регистрационную форму и столкнулся с проблемой. После того, как пользователь заполняет поля, я выполняю проверку правильности с помощью регулярного выражения. Каждая функция проверки будет выдавать сообщение об ошибке. Даже если я намеренно вызываю все эти ошибки, при отправке формы появится только одна. Вот мой код:

 function RegisterScreen(): JSX.Element{


    const pwInput = React.createRef();
    const [firstName, setFirstName] = useState('');
    const [lastName, setLastName] = useState('');
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [confirmPassword, setConfirmPassword] = useState('');
    const [birthday, setBirthday] = useState<Date | null>(null);
    const [isSelected, setSelection] = useState(false); 
    const [openTerms, setOpenTerms] = useState(false);
    const [emailError, setEmailError] = useState<string | null>(null);
    const [passError, setPassError] = useState<string | null>(null);
    const [confPassError, setConfPassError] = useState<string | null>(null);
    const [isTermsChecked, setIsTermsChecked] = useState<string | null>(null); 
   

    function _validateEmail(): boolean{
        const re = /^(([^<>()[]\.,;:s@"] (.[^<>()[]\.,;:s@"] )*)|(". "))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9] .) [a-zA-Z]{2,}))$/;
        if(re.test(email))
            return true;
        else{
            setEmailError(RegisterErrors.emailError);
            return false;
        }
       
    }
    function _validatePw(): boolean{
        const re = /^(?=.*d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/
        
        if(re.test(password)) return true;
        else{
            setPassError(RegisterErrors.pwWeak);
            return false
        }
    }
    function _pwMatch(): boolean{

        if(password == confirmPassword){
            
            return true;
        }else{
            setConfPassError(RegisterErrors.pwConfirmMatchError);
            return false;
        }
    }

    function _validateAge(): boolean{
            return true;
    }

    function _validateTermsAndCond(): boolean{
            return true;
    } 

   

    function validateFieldsAndRegister(){

        console.log(email);
        console.log(password);
        console.log(confirmPassword);
        
        if( _validateEmail() amp;amp; _validatePw() amp;amp; _validateAge() amp;amp; _validateTermsAndCond() amp;amp; _pwMatch()){

                //trimite la firebase
                console.log("okay")

        }else{

           console.log('wrong fields')

            
        }

    }


  return(
        <SafeAreaView style={{flex:1}}>
     
        <ScrollView>
        <View style = {{flex:1, alignItems: "center", justifyContent:"center",backgroundColor:"white"}}>
        
        <TermsAndConditions open = {openTerms} close = { ()=>setOpenTerms(false) }/>
    
        <Image source={require("../../images/logoR.png")} style={styles.logoImg}/>
        

        <Text style={styles.mainText}>Register</Text>
        


        
          <Input  
            containerStyle={styles.inputView}
            inputContainerStyle={{backgroundColor:"white"}}
            placeholder="First Name..." 
            placeholderTextColor="rgb(0,0,0)"
            errorStyle={{ color: 'red' }}
            onChangeText={text => setFirstName(text)}/>
        
      
          <Input  
            containerStyle={styles.inputView}
            placeholder="Last Name..." 
            placeholderTextColor="rgb(0,0,0)"
            errorStyle={{ color: 'red' }}
            onChangeText={text => setLastName(text)}/>
        
       
        
          <Input 
            onFocus={()=>{setPassError(null)}}
            
            secureTextEntry
            containerStyle={styles.inputView}
            placeholder="Password..." 
            placeholderTextColor="rgb(0,0,0)" 
            errorStyle={{ color: 'red' }}
            errorMessage={passError}
            onChangeText={text =>setPassword(text)}/>
       

      
          <Input  
            secureTextEntry
            onFocus = {()=>{setConfPassError(null)}}
            containerStyle={styles.inputView}
            placeholder="Confirm Password..." 
            placeholderTextColor="rgb(0,0,0)"
            errorStyle={{ color: 'red' }}
            errorMessage={confPassError}
            
            onChangeText={text =>setConfirmPassword(text)}/>
      

       
          <Input  
            containerStyle={styles.inputView}
            onFocus={()=>{setEmailError(null)}}
            placeholder="Email..." 
            placeholderTextColor="rgb(0,0,0)"
            errorStyle={{ color: 'red' }}
            errorMessage={emailError}
            
            onChangeText={text => setEmail(text)}/>
       

         
        <View style={styles.checkboxContainer}>
            <CheckBox
            value={isSelected}
            onValueChange={()=>{setSelection(!isSelected)}}
            style={styles.checkbox}
          
            />
    
            <TouchableOpacity onPress = {()=> {setOpenTerms(true);}}>
            <Text style={styles.label}>Terms and Conditions</Text>
            </TouchableOpacity>

        </View>

        <TouchableOpacity  style={{...styles.inputView, ...styles.regBtnContainer}} onPress = {()=>{validateFieldsAndRegister()}}>
            <Text style = {styles.txtRegBtn}>Register</Text>
        </TouchableOpacity>
        

        </View>
        
</ScrollView>

</SafeAreaView>
    )
}

  

Скриншот

Если я вызову функции проверки перед инструкцией if в VALIDATEFIELDS и Register, это сработает. В 3 полях будут отображаться ошибки.

Вот ошибки, которые я показываю

 export const RegisterErrors = {

    emailError: "You must enter a valid email",
    pwConfirmMatchError:"The passwords you entered are not the same",
    termsAndCondError:"You must read and agree with the Terms and Conditions",
    birthdayError:"You must be 21 or older",
    pwWeak:"Your password must contain: a lowercase letter, a acapital letter, a number and minimum 8 characters"

}
  

Комментарии:

1. Приняли ли вы во внимание асинхронный характер обновлений состояния? Это может быть просто состояние гонки.

2. похоже, что как только функция проверки возвращает false, остальные не выполняются, потому что в этом нет смысла .. общий результат будет false, поскольку я использую оператор AND . Есть ли такое в typescript / javascript?

3. Да, JS приведет к короткому замыканию.

4. Это особенность javascript для вычисления короткого замыкания

Ответ №1:

Вот фрагмент, иллюстрирующий часть того, что происходит. Это иллюстрирует три различные стратегии обновления состояния.

Первый группирует все три setState() вызова в один вызов функции и не использует if() инструкцию для проверки результатов. Изменения состояния отображаются немедленно.

Второй разделяет обновления на три отдельных вызова async функций, которые, в свою очередь, await перед вызовом имеют тайм-аут setState() . Эти вызовы выполняются так, как это делает OP из if() инструкции. Каждая асинхронная функция возвращает обещание, которое оценивается как истинное, и мы видим, что ‘promise’ регистрируется в консоли. Затем поступают результаты таймаутов.

Третий случай наиболее близок к вопросу OP. Он также выполняет отдельные вызовы из if() инструкции, но они синхронны и возвращают false . Первый вызов завершает свой setState() вызов, возвращает false и останавливает дальнейшие вызовы изнутри if. Поскольку состояние изменилось, React вызывает визуализацию, и мы видим изменение одного состояния.

Результатом всего этого является то, что, по-видимому, лучше всего либо выполнить вызовы проверки перед if() инструкцией, либо, что еще лучше, собрать все результаты проверки, а затем сгруппировать все setState() вызовы в одну функцию. Это позволит React более эффективно группировать эти изменения и вызывать как можно меньше рендеров.

(извините за загроможденный фрагмент, встроенный babel, похоже, не справляется async/await )

 .container {
  display: flex;
  
}
.example {
  margin: 1rem;
  padding: 1rem;
  border: 1px solid gray;
}
button {
  margin: 1rem;
}  
 <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
  const { useState } = React
  
  function App() {
  const [stateOne, setStateOne] = useState(1);
  const [stateTwo, setStateTwo] = useState(1);
  const [stateThree, setStateThree] = useState(1);
  
  const cycleStateSingle = () => {
    setStateOne((state) => state   1);
    setStateTwo((state) => state   1);
    setStateThree((state) => state   1);
  }
  
  const [stateOnePromise, setStateOnePromise] = useState(1);
  const [stateTwoPromise, setStateTwoPromise] = useState(1);
  const [stateThreePromise, setStateThreePromise] = useState(1);
  
  const cycleOne = async () => {
      await new Promise(resolve => setTimeout(resolve, 1000));
      setStateOnePromise((state) => state   1);
  }
  
  const cycleTwo = async () =>{
      await new Promise(resolve => setTimeout(resolve, 2000));
      setStateTwoPromise((state) => state   1);
  }
  
  const cycleThree = async () =>{
      await new Promise(resolve => setTimeout(resolve, 3000));
      setStateThreePromise((state) => state   1);
  }
  
  const cycleStatePromise = () => {
    if (cycleOne(true) amp;amp; cycleTwo(true) amp;amp; cycleThree(true)) {
      console.log('promise');
    }
  }
  
  const [stateOneStepped, setStateOneStepped] = useState(1);
  const [stateTwoStepped, setStateTwoStepped] = useState(1);
  const [stateThreeStepped, setStateThreeStepped] = useState(1);
  
  const cycleOneStepped = () => {
      setStateOneStepped((state) => state   1);
      return false;
  }
  
  const cycleTwoStepped = () =>{
      setStateTwoStepped((state) => state   1);
      return false;
  }
  
  const cycleThreeStepped = () =>{
    setStateThreeStepped((state) => state   1);
    return false;
  }
  
  const cycleStateStepped = () => {
    if (cycleOneStepped() amp;amp; cycleTwoStepped() amp;amp; cycleThreeStepped()) {
      console.log('stepped');
    }
  }
  
  return (
  <div className="container">
    <div className="example">
      <div className="state-1">State 1: {stateOne}</div>
      <div className="state-2">State 2: {stateTwo}</div>
      <div className="state-3">State 3: {stateThree}</div>
      <button type="button" onClick={cycleStateSingle}>Single State Change</button>
    </div>
    <div className="example">
      <div className="state-1">State 1: {stateOnePromise}</div>
      <div className="state-2">State 2: {stateTwoPromise}</div>
      <div className="state-3">State 3: {stateThreePromise}</div>
      <button type="button" onClick={cycleStatePromise}>Promise State Change</button>
    </div>
    <div className="example">
      <div className="state-1">State 1: {stateOneStepped}</div>
      <div className="state-2">State 2: {stateTwoStepped}</div>
      <div className="state-3">State 3: {stateThreeStepped}</div>
      <button type="button" onClick={cycleStateStepped}>Stepped State Change</button>
    </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
</script>

<div id="root"></div>