为什么我不能在功能组件中更新状态(使用钩子)?

Why can't I have updated states in function component (using hook)?

我有两个输入的登录功能组件。它是受控组件,因此电子邮件和密码绑定到状态(用户)。 Input是我用来代替input标签的一个组件(重构输入)。我可以使用 handleInputChange 事件处理程序使用输入值更改状态用户(电子邮件和密码),我也可以使用 handleSubmit 处理程序提交表单。

一切都很好,直到我尝试使用 yup 验证表单并捕获错误。我声明了错误状态以保存我得到的错误。我想捕获错误并在“div className="alert"" 中显示,并且我想在不存在错误时 post 用户访问服务器。我在 validate() 函数中看到与 yup 相关的错误,但是当我更改错误状态 (setErrors([...er.errors])) 时,我发现错误状态为空 (console.log(errors.length)).

这是登录组件:

import axios from "axios";
import queryString from "query-string"
import { useEffect, useRef,useState } from "react";
import React from "react"
import {useLocation, useRouteMatch,useParams} from "react-router-dom"
import Input from "./input";
import * as yup from 'yup';
const Login = () => {
    useEffect(async()=>{
   
   
       
        console.log(errors)
    },[errors])

    var [user,setUser]=useState({email:'',password:''});
 var [errors,setErrors]=useState([])
 let schema=yup.object().shape({
    email:yup.string().email("ایمیل نامعتبر است").required("فیلد ایمیل الزامیست"),
    password:yup.string().min(8,"رمز عبور باید حداقل 8 رقم باشد")
})
const validate=async()=>{
    try  {
       const resultValidate=await schema.validate(user, { abortEarly: false })
       
    }
    catch(er){
       console.log(er.errors)
      setErrors([...er.errors])
     
       
    }
   }
  const  handleSubmit= async(e)=>{
      
e.preventDefault();
await validate();
console.log(errors.length)
if(errors.length===0){
    alert("X")
   const response= await axios.post("https://reqres.in/api/login",user)
   console.log(response)
 }
  }

const handleInputChange=async(e)=>{
 
  setUser({...user,[e.currentTarget.name]:e.currentTarget.value})


}
    return ( 
    <>
     <div id="login-box" className="col-md-12">
                        
            
                        {errors.length!==0  && (<div className="alert">
                            <ul>
                               {errors.map((element,item)=>{
                                    return(
                                    <>
                                     <li key={item}>
                                           {element}
                                    </li>
                                    </>
                                   )
                                })}
                            </ul>
                        </div>) }
                        <form onSubmit={handleSubmit} id="login-form" className="form" action="" method="post">
                            <h3 className="text-center text-info">Login</h3>
                            <Input  onChange={handleInputChange} name="email" id="email" label="نام کاربری" value={user.email}/>
                            <Input  name="password" onChange={handleInputChange} id="password" value={user.password} label="رمز عبور"/>
                            
                         
                          
                            {/* <div id="register-link" className="text-right">
                                <a href="#" className="text-info">Register here</a>
                            </div> */}
                            <input type="submit"   className="btn btn-primary" value="ثبت"/>
                        </form>
                    </div>
    </>
    );
}
 
export default Login;

这是输入组件:

import {Component} from "react"
class Input extends Component {
    render() { 
        return <>
        
   <div className="form-group">
   <label htmlFor="username" className="text-info">{this.props.label}</label><br/>
                                <input type="text" onChange={this.props.onChange} name={this.props.name} id={this.props.id} className="form-control" value={this.props.value} />
   </div>

        </>;
    }
}
 
export default Input;

我知道 setStates(在我的组件 setErrors 中)是异步的并且有延迟。我尝试使用简单的数组变量(命名错误)代替状态和挂钩,但你猜怎么着,当我更改错误变量时它没有重新呈现页面!当然,我无法使用这种方式在页面中看到错误。

我尝试使用 useEffect() 解决此问题,我决定在 useEffect 中检查验证错误和 post 而不是 handleSubmit 处理程序:

 useEffect(async()=>{
   
    if(errors.length===0){

        const response= await axios.post("https://reqres.in/api/login",user)
        console.log(response)
      }
   
    console.log(errors)
}, [errors])

现在我在输入无效时看到错误。当我输入有效值时,仍然有相同的错误! 看起来我无法更新错误状态,即使在输入有效值后我也会收到以前的错误!我尽可能不使用基于 class 的组件。我该怎么办?

如果输入值经过验证,您可以 return 为真,否则为假,来自 validate 函数,如下所示:

const validate = async () => {
try {
  const resultValidate = await schema.validate(user, { abortEarly: false });
  return true;
} catch (er) {
  console.log(er.errors);
  setErrors([...er.errors]);
  return false;
}
};

现在在 handleSubmit 函数中你必须修改一下:

 const handleSubmit = async (e) => {
  e.preventDefault();
  const isValid = await validate();

 console.log(errors.length);

 if (isValid) {
  alert("X");
  const response= await axios.post("https://reqres.in/api/login",user)
  console.log(response)
  setErrors([]);  //so that the previous errors are removed
 }
 };

问题

您面临的问题是 React 状态更新是 异步 处理的。这并不意味着状态更新是 async 并且可以等待。在 下一个 渲染周期之前,您入队的 errors 状态将不可用。

const validate = async () => {
  try  {
    const resultValidate = await schema.validate(user, { abortEarly: false });       
  } catch(er) {
    console.log(er.errors);
    setErrors([...er.errors]); // (2) <-- state update enqueued
  }
}

const handleSubmit = async (e) => {
  e.preventDefault();

  await validate(); // (1) <-- validate called and awaited

  console.log(errors.length); // <-- (3) errors state from current render cycle

  if (errors.length === 0) {
    alert("X");
    const response = await axios.post("https://reqres.in/api/login", user);
    console.log(response);
  }
}

解决方案

我建议从 validate 返回一个“错误”对象,如果您愿意,您可以稍后将任何状态更新加入队列。

const validate = async () => {
  const errors = [];
  try  {
    await schema.validate(user, { abortEarly: false });
  } catch(er) {
    console.log(er.errors);
    errors.push(...er.errors);
  }
  return errors;
}

const handleSubmit = async (e) => {
  e.preventDefault();

  const errors = await validate();

  console.log(errors.length);

  if (!errors.length) {
    alert("X");
    const response = await axios.post("https://reqres.in/api/login", user);
    console.log(response);
  } else {
    setErrors(prevErrors => [...prevErrors, ...errors]);
  }
}