为啥我不能更新函数组件中的状态(使用钩子)?

Posted

技术标签:

【中文标题】为啥我不能更新函数组件中的状态(使用钩子)?【英文标题】:Why can't I have updated states in function component (using hook)?为什么我不能更新函数组件中的状态(使用钩子)? 【发布时间】:2022-01-07 18:59:19 【问题描述】:

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

在我尝试使用 yup 验证表单并捕获错误之前,一切都很好。我声明了错误状态以保存我得到的错误。我想捕获错误并显示在“div className="alert"”中,并且我想在不存在错误时将用户发布到服务器。我在 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 中发布,而不是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])

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

【问题讨论】:

【参考方案1】:

如果输入值经过验证,您可以返回 true,否则返回 false,来自 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
 
 ;

【讨论】:

【参考方案2】:

问题

您面临的问题是 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]);
  

【讨论】:

以上是关于为啥我不能更新函数组件中的状态(使用钩子)?的主要内容,如果未能解决你的问题,请参考以下文章

OnSubmit 函数不会更新 React 中的反应钩子状态变量

反应钩子。无法对未安装的组件执行 React 状态更新

为啥我不能将其转换为自定义的 React 钩子?

更新时反应钩子状态未定义

使用上下文和钩子更新未安装组件的状态 - 反应原生

为啥在反应的“useState”钩子中一遍又一遍地设置初始状态