Formik 和 yup 验证不适用于所需的

Formik and yup validation not working for required

我使用 Formik 和 Yup 在 React 中验证我的登录表单。从下面的代码中可以看出,我检查输入是否为空,然后调用我的后端来检查用户是否存在。对后端的调用有效,因为如果用户不存在,我会在 ID 为“invalidUser”的 div 中报告错误(我不知道这是否是最正确的解决方案,我想到了一种插入方式formik 控制形式的错误,但我没有成功),而 required() Yup 不起作用,因为当我不在输入中写入任何内容时它不会报告错误。为什么?你有解决方案吗?还有我的控制方案,如果用户存在与否,是否可以顺利进行或有更好的?

index.js:

import React from 'react'
import {useNavigate} from "react-router-dom";
import {Form, Button} from 'react-bootstrap';
import {LoginWrapper, LoginForm, IconImg, SocialLogin, ErrorMessage} from './AccessoElements';
import GoogleLogin from 'react-google-login';
import GoogleButton from 'react-google-button';
import $ from 'jquery';
import FacebookLogin from 'react-facebook-login';
import {FaFacebookF} from 'react-icons/fa';
import {Formik} from 'formik';
import * as yup from 'yup';

const responseGoogle = (response) => {
    console.log(response);
    console.log(response.profileObj);
}

const responseFacebook = (response) => {
    console.log(response);
    console.log(response.profileObj);
}

const Accesso = () => {
    const schemaLogin = yup.object().shape({
        username: yup.string().required("L'username o l'email è obbligatorio."),
        password: yup.string().required('La password è obbligatoria.'),
    }).test({
        name: "invalidUser",
        test: async (values) => {
            const requestOptions = {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    "username": values.username,
                    "password": values.password
                })
            };
            let response = await fetch("http://localhost:8080/api/v1/auth/signin/available", requestOptions)
            if (response.ok) {
                $("#invalidUser").css("display", "none");
                return true;
            } else {
                $("#invalidUser").css("display", "flex");
                return this.createError({
                    message: "Email/Username or password invalid.",
                })
            }
        }
    })

    const navigate = useNavigate();

    return (
        <LoginWrapper>
            <LoginForm>    
                <IconImg src={require('../../../images/avatarUser.png').default} alt="icon" />
                <Formik
                    validationSchema={schemaLogin}
                    validateOnChange={false}
                    validateOnBlur={false}
                    onSubmit={(values) => {
                        const requestOptions = {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({"username": values.username, "password": values.password})
                        };
                        fetch("http://localhost:8080/api/v1/auth/signin", requestOptions)
                        .then(response => response.json())
                        .then(data => {
                            sessionStorage.setItem("username", data['username']);
                            sessionStorage.setItem("email", data['email']);
                            sessionStorage.setItem("roles", data['roles']);
                            sessionStorage.setItem("isLoggedIn", true);  
                            sessionStorage.setItem("tokenType", data['tokenType']);
                            sessionStorage.setItem("accessToken", data['accessToken']);
                            navigate("/");
                        })
                        .catch(err => console.log(err))
                    }}
                    initialValues={{
                        username: '',
                        password: '',
                    }}
                    >
                    {({
                        handleSubmit,
                        handleChange,
                        values,
                        errors,
                    }) => (
                <Form noValidate onSubmit={handleSubmit}>
                    <Form.Group className="position-relative mb-3" controlId="formBasicEmail">
                    <Form.Control
                        type="text"
                        name="username"
                        placeholder="Username"
                        value={values.username}
                        onChange={handleChange}
                        isInvalid={!!errors.username}
                    />
                    <Form.Control.Feedback type="invalid" tooltip>
                        {errors.username}
                    </Form.Control.Feedback>
                    </Form.Group>
                    <Form.Group className="position-relative mb-3" controlId="formBasicPassword">
                    <Form.Control
                        type="password"
                        name="password"
                        placeholder="Password"
                        value={values.password}
                        onChange={handleChange}
                        isInvalid={!!errors.password}
                    />
                    <Form.Control.Feedback type="invalid" tooltip>
                        {errors.password}
                    </Form.Control.Feedback>
                    </Form.Group>
                    <Button variant="primary w-100" type="submit">Login</Button>
                </Form>
                )}
            </Formik>
            <ErrorMessage id="invalidUser">
                <p>Email/Username o password invalidi.</p>
            </ErrorMessage>
            </LoginForm>
            <div className="divider"><span></span><span>Oppure</span><span></span></div>
            <SocialLogin>
                <div className="container">
                    <FacebookLogin
                        appId="1156207558121098"
                        autoLoad={false}
                        fields="name,email,picture"
                        callback={responseFacebook}
                        textButton="Accedi con Facebook"
                        icon={<FaFacebookF  style={{marginRight: "10px", marginBottom: "3px"}}></FaFacebookF>}
                        cssClass="btnFacebook"
                        language="it_IT"
                    />
                </div>
                <div className="container">
                    <GoogleLogin
                        clientId="459333865802-8u7ted62or2vluagnus58250np433omm.apps.googleusercontent.com"
                        buttonText="Accedi con Google"
                        onSuccess={responseGoogle}
                        onFailure={responseGoogle}
                        cookiePolicy={'single_host_origin'}
                        isSignedIn={true}
                        language="it_IT"
                        render={renderProps => (
                            <GoogleButton 
                                onClick={renderProps.onClick}
                                label='Accedi con Google'
                                style={{fontWeight: "700", fontFamily: "Helvetica, sans-serif", WebkitFontSmoothing: "antialiased", justifyContent: "center", minWidth: "240px"}}
                            />
                        )}
                    />
                </div>
            </SocialLogin>
        </LoginWrapper>
    )
}

export default Accesso

AccessoElements.js:

import styled from 'styled-components';

export const LoginWrapper = styled.div`
    align-items: center;
    min-height: 100vh;
    background: #0c0c0c;
`

export const LoginForm = styled.div`
    width: 25%;
    margin: 0 auto;
    margin-bottom: 50px;
    padding-top: 140px;
    text-align: center;

    @media screen and (max-width: 968px) {
        width: 45%;
    }

    @media screen and (max-width: 768px) {
        width: 55%;
    }
`

export const SocialLogin = styled.div`
    display: flex;
    width: 30%;
    margin: 0 auto;
    justify-content: center;

    @media screen and (max-width: 968px) {
        width: 55%;
    }

    @media screen and (max-width: 600px) {
        flex-wrap: wrap;
    }
`

export const IconImg = styled.img`
    width: 70px;
    height: 70px;
    margin-bottom: 2rem;
`

export const ErrorMessage = styled.div`
    display: none;
    color: #dc3545;
    margin-top: 1rem;

    p {
        margin-bottom: 0rem;
    }
`

你应该避免在使用 React 时直接访问 DOM,所以你应该将 invalidUser 信息保存在一个状态中,然后 show/hide ErrorMessage 取决于那个.

您也可以在实际提交之前直接对用户进行验证。

因此您可以执行以下操作:

import React from 'react'
import {useNavigate} from "react-router-dom";
import {Form, Button} from 'react-bootstrap';
import {LoginWrapper, LoginForm, IconImg, SocialLogin, ErrorMessage} from './AccessoElements';
import GoogleLogin from 'react-google-login';
import GoogleButton from 'react-google-button';
import $ from 'jquery';
import FacebookLogin from 'react-facebook-login';
import {FaFacebookF} from 'react-icons/fa';
import {Formik} from 'formik';
import * as yup from 'yup';

const responseGoogle = (response) => {
    console.log(response);
    console.log(response.profileObj);
}

const responseFacebook = (response) => {
    console.log(response);
    console.log(response.profileObj);
}

const Accesso = () => {
    const [invalidUser, setInvalidUser] = React.useState(false)
    const testUser = async (values) => {
            const requestOptions = {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    "username": values.username,
                    "password": values.password
                })
            };
            let response = await fetch("http://localhost:8080/api/v1/auth/signin/available", requestOptions)
            setInvalidUser(!response.ok)
            return response.ok
        }
    const schemaLogin = yup.object().shape({
        username: yup.string().required("L'username o l'email è obbligatorio."),
        password: yup.string().required('La password è obbligatoria.'),
    })

    const navigate = useNavigate();

    return (
        <LoginWrapper>
            <LoginForm>    
                <IconImg src={require('../../../images/avatarUser.png').default} alt="icon" />
                <Formik
                    validationSchema={schemaLogin}
                    validateOnChange={false}
                    validateOnBlur={false}
                    onSubmit={async (values) => {
                        const isValid = await testUser(values)
                        if (isValid) {

                          const requestOptions = {
                              method: 'POST',
                              headers: { 'Content-Type': 'application/json' },
                              body: JSON.stringify({"username": values.username, "password": values.password})
                          };
                          fetch("http://localhost:8080/api/v1/auth/signin", requestOptions)
                          .then(response => response.json())
                          .then(data => {
                              sessionStorage.setItem("username", data['username']);
                              sessionStorage.setItem("email", data['email']);
                              sessionStorage.setItem("roles", data['roles']);
                              sessionStorage.setItem("isLoggedIn", true);  
                              sessionStorage.setItem("tokenType", data['tokenType']);
                              sessionStorage.setItem("accessToken", data['accessToken']);
                              navigate("/");
                          })
                          .catch(err => console.log(err))
                        }
                    }}
                    initialValues={{
                        username: '',
                        password: '',
                    }}
                    >
                    {({
                        handleSubmit,
                        handleChange,
                        values,
                        errors,
                    }) => (
                <Form noValidate onSubmit={handleSubmit}>
                    <Form.Group className="position-relative mb-3" controlId="formBasicEmail">
                    <Form.Control
                        type="text"
                        name="username"
                        placeholder="Username"
                        value={values.username}
                        onChange={handleChange}
                        isInvalid={!!errors.username}
                    />
                    <Form.Control.Feedback type="invalid" tooltip>
                        {errors.username}
                    </Form.Control.Feedback>
                    </Form.Group>
                    <Form.Group className="position-relative mb-3" controlId="formBasicPassword">
                    <Form.Control
                        type="password"
                        name="password"
                        placeholder="Password"
                        value={values.password}
                        onChange={handleChange}
                        isInvalid={!!errors.password}
                    />
                    <Form.Control.Feedback type="invalid" tooltip>
                        {errors.password}
                    </Form.Control.Feedback>
                    </Form.Group>
                    <Button variant="primary w-100" type="submit">Login</Button>
                </Form>
                )}
            </Formik>
            {invalidUser && <ErrorMessage id="invalidUser">
                <p>Email/Username o password invalidi.</p>
            </ErrorMessage>}
            </LoginForm>
            <div className="divider"><span></span><span>Oppure</span><span></span></div>
            <SocialLogin>
                <div className="container">
                    <FacebookLogin
                        appId="1156207558121098"
                        autoLoad={false}
                        fields="name,email,picture"
                        callback={responseFacebook}
                        textButton="Accedi con Facebook"
                        icon={<FaFacebookF  style={{marginRight: "10px", marginBottom: "3px"}}></FaFacebookF>}
                        cssClass="btnFacebook"
                        language="it_IT"
                    />
                </div>
                <div className="container">
                    <GoogleLogin
                        clientId="459333865802-8u7ted62or2vluagnus58250np433omm.apps.googleusercontent.com"
                        buttonText="Accedi con Google"
                        onSuccess={responseGoogle}
                        onFailure={responseGoogle}
                        cookiePolicy={'single_host_origin'}
                        isSignedIn={true}
                        language="it_IT"
                        render={renderProps => (
                            <GoogleButton 
                                onClick={renderProps.onClick}
                                label='Accedi con Google'
                                style={{fontWeight: "700", fontFamily: "Helvetica, sans-serif", WebkitFontSmoothing: "antialiased", justifyContent: "center", minWidth: "240px"}}
                            />
                        )}
                    />
                </div>
            </SocialLogin>
        </LoginWrapper>
    )
}

export default Accesso

当然,将上面的代码当作一个提示来考虑,以便开始为自己寻找最佳解决方案。