I use Formik and Yup to validate my login form in React. As you can see from the code below, I check that the inputs are not empty and then call my backend to check if the user exists. The call to the backend works, because if the user does not exist I report the error in the div with id "invalidUser" (I do not know if this is the most correct solution, I thought of a way to insert the error in the form of control of formik but I have not succeeded) while the required() Yup doesn’t work because it doesn’t report errors when I don’t write anything in the inputs. Why? Do you have a solution? Also my control solution if the user exists or not can go well or there is a better one?
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;
}
`
CodePudding user response:
you should avoid to access directly the DOM when using React so you should save the invalidUser
information inside a state and then show/hide the ErrorMessage
depending on that.
You could also do the validation of the user directly before the actual submit.
So you could do something like the following:
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
Of course consider the above code just like an hint in order to start to find the best solution for yourself.