为啥按钮只有在第二次点击后才起作用? (反应)

Posted

技术标签:

【中文标题】为啥按钮只有在第二次点击后才起作用? (反应)【英文标题】:Why button works only after second click? (reactjs)为什么按钮只有在第二次点击后才起作用? (反应) 【发布时间】:2020-08-19 08:46:14 【问题描述】:

我有登录按钮,如果每个输入字段第一次填写正确(如果有效),那么它可以正常工作,但如果没有,我必须点击 3 次才能登录(第一次我收到消息错误该输入无效,第二次点击清除该错误,第三次让我登录)。

import React,  useRef, useContext, useState  from 'react'
import  AppContext  from '../context/AppContext'
import  useHistory  from 'react-router-dom'

function Login() 

    const  setUser  = useContext(AppContext)

    const history = useHistory()

    const usernameRef = useRef<htmlInputElement>(null)
    const emailRef = useRef<HTMLInputElement>(null)
    const passwordRef = useRef<HTMLInputElement>(null)

    const [valid, setValid] = useState( usernameErr: '', emailErr: '', passwordErr: '' )

    const handleValidation = () => 
        let usernameMsg = ''
        let passwordMsg = ''
        let emailMsg = ''

        if (usernameRef.current?.value === "")
            usernameMsg = "Username is not valid"
        if (!emailRef.current?.value.includes('@') && !emailRef.current?.value.includes('.'))
            emailMsg = "Email is not valid"
        if (passwordRef.current?.value !== undefined && passwordRef.current.value.length < 5)
            passwordMsg = "Password must contain at least 5 characters"

        setValid(
            usernameErr: usernameMsg,
            emailErr: emailMsg,
            passwordErr: passwordMsg
        )
    

    const handleLogin = () => 
        handleValidation()
        console.log("validation: ", valid)
        if (usernameRef.current?.value && emailRef.current?.value && passwordRef.current?.value) 
            if (valid.usernameErr === '' && valid.emailErr === '' && valid.passwordErr === '') 
                setUser(
                    
                        username: usernameRef.current.value,
                        password: passwordRef.current.value,
                        email: emailRef.current.value,
                        isLogin: true
                    )
                history.goBack()
            
        
    

    return (
        <div className="login-outter page">
            <div className="login-inner">
                <p className="login-title">Log in here</p>
                <input type="text" placeholder="Username" ref=usernameRef className="about-input"/>
                <p className="valid">
                    valid.usernameErr
                </p>
                <input type="text" placeholder="Email" ref=emailRef className="about-input"/>
                <p className="valid">
                    valid.emailErr
                </p>
                <input type="password" placeholder="Password" ref=passwordRef className="about-input"/>
                <p className="valid">
                    valid.passwordErr
                </p>
                <button className="login-btn" onClick=handleLogin>Login</button>
            </div>
        </div>
    )


export default Login

这里是代码沙箱: https://codesandbox.io/s/flamboyant-torvalds-tevby?file=/src/components/pages/Login.tsx

我认为这是因为useState,但不知道究竟是什么,为什么以及如何解决这个问题。

【问题讨论】:

React 状态钩子更新是异步的,因此您无法保证在调用 handleValidation() 和实际检查状态之间会更新状态。 【参考方案1】:

这是因为 React 状态钩子更新是异步的,正如我在 cmets 中提到的。这是一个示例,说明如何通过稍微修改当前代码来解决此问题: (不是最优雅的解决方案,但它对您的代码进行了最小的更改并且可以正常工作)

    const handleValidation = () => 
        let usernameMsg = ''
        let passwordMsg = ''
        let emailMsg = ''

        if (usernameRef.current?.value === "")
            usernameMsg = "Username is not valid"
        if (!emailRef.current?.value.includes('@') && !emailRef.current?.value.includes('.'))
            emailMsg = "Email is not valid"
        if (passwordRef.current?.value !== undefined && passwordRef.current.value.length < 5)
            passwordMsg = "Password must contain at least 5 characters"

        setValid(
            usernameErr: usernameMsg,
            emailErr: emailMsg,
            passwordErr: passwordMsg
        )
        return (usernameMsg === '' && passwordMsg === '' && emailMsg === '')
    

    const handleLogin = () => 
        if (handleValidation() && usernameRef.current?.value && emailRef.current?.value && passwordRef.current?.value) 
                setUser(
                    
                        username: usernameRef.current.value,
                        password: passwordRef.current.value,
                        email: emailRef.current.value,
                        isLogin: true
                    )
                history.goBack()
        
    

这里是你的链接:https://codesandbox.io/s/strange-lamport-olcy0?file=/src/components/pages/Login.tsx

【讨论】:

谢谢!你能解释一下为什么返回这个(usernameMsg === '' &amp;&amp; passwordMsg === '' &amp;&amp; emailMsg === '') 吗? 如果您的 handleValidation() 函数中没有错误,这将返回 true。它返回 handleValidation() 函数的结果,而不依赖于状态,因为状态更新是异步的。这样您就可以查看 handleValidation() 是否返回 true 并替换您之前的这一行:if (valid.usernameErr === '' &amp;&amp; valid.emailErr === '' &amp;&amp; valid.passwordErr === '')【参考方案2】:

handleLogin() 从函数被执行(按钮被点击)的渲染周期中获取valid 状态值。

这意味着当您在handleLogin() 中调用handleValidation() 时,它不会立即影响同一上下文中的valid 状态,但会影响下一个 渲染周期中的状态,其中一个新的handleLogin 函数将被创建(在单击按钮之前不会执行)。

解决方法是让handleValidation()这样的验证器函数直接返回一个验证结果,然后你可以在handleLogin()里面设置错误消息状态

【讨论】:

以上是关于为啥按钮只有在第二次点击后才起作用? (反应)的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 ViewController 在第二次调用后才发布,iOS ARC?

Jquery Click 事件在第二次点击时触发,但不是第一次

为啥提交按钮仅在我的示例中按下两次时才起作用?

用于获取数据的自定义反应钩子在第二次点击时未提供数据

第二次添加视图时按钮点击没有反应

第二次点击后出现文本字段的键盘(不时)