Reputation: 136
I have a login in react,
import React, { Fragment, useState } from 'react';
import { useHistory } from "react-router-dom";
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { auth } from '../actions';
export const Login = () => {
let history = useHistory();
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState({email: '', password: ''});
const validEmailRegex = RegExp(/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|.
(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i);
const handleChange = e => {
e.persist();
const { name, value } = e.target;
let validationError = error;
switch(name) {
case 'email':
setEmail(value);
validationError.email = validEmailRegex.test(value) ? '' : 'Email is not valid';
break;
case 'password':
setPassword(value)
validationError.password = value.length < 8 ? 'Password must be 8 characters
long!': '';
break;
case 'submit':
validationError.email = email.length < 1 ? 'Email is required' : '';
validationError.password = password.length < 1 ? 'Password is required' : '';
default:
break;
};
setError(validationError);
console.log('in change', error)
};
const validateForm = (errors) => {
let valid = true;
Object.values(errors).forEach(
// if we have an error string set valid to false
(val) => val.length > 0 && (valid = false)
);
return valid;
};
const validate = () => {
console.log('email,password', email, password);
let validationError = error;
if(!email){
validationError.email = 'Email is required';
}
if(!password){
validationError.password = 'Password is required';
}
setError(validationError);
console.log('in validate',error)
};
const onSubmit = e => {
e.preventDefault();
validate();
console.log('error on submit', error);
if(validateForm(error)) {
dispatch(auth(email, password, true));
history.replace('/home');
}else{
console.error('Invalid Form', error)
}
};
return (
<Fragment>
<div className="w-full max-w-sm container mt-20 mx-auto">
<form onSubmit={onSubmit}>
<div className="w-full mb-5">
<label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="email">
Email
</label>
<input className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:text-gray-600" value={email} name='email' onChange={(e) => handleChange(e)} type="text" placeholder="Email" />
{ error && <span style={{color: "red"}}>{error['email']}</span>}
</div>
<div className="w-full mb-5">
<label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="password">
Password
</label>
<input className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:text-gray-600" value={password} name='password' onChange={(e) => handleChange(e)} type="password" placeholder="Password" />
{ error && <span style={{color: "red"}}>{error['password']}</span>}
</div>
<div className="flex items-center justify-between">
<button className="mt-5 bg-green-400 w-full hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Login
</button>
</div>
<div className="text-center mt-4 text-gray-500"><Link to='/'>Cancel</Link></div>
</form>
</div>
</Fragment>
)
}
I have added field validation in onchange and in submit, the onchange validation is working correctly and showing erros. Validation on submit is also wokring fine, but still the component is not showing error when I try submit without any change in fields.
I am new to react, I dont know if this is the correct way to do that. Thanks in advance.
Upvotes: 1
Views: 602
Reputation: 6952
I put your code in a code sandbox and it seems to work just fine. Note that i removed className
properties and commented stuff that is not necessary to test your issue like redux
imports.
Since I removed the className
properties this could be an CSS
issue, where your error span
is actually rendered but not visible (check out the dev tools of your browser to see if the span
is really not there).
Also I would recommend you to use a library if you use a lot of forms, since state handling + validation could get quite complicated and there are plenty solutions out there.
I wrote my own library - react-fluent-form - feel free to check that out.
EDIT
The issue here is that when you update the error
object using setError
you always pass the same object reference:
// this is not doing a copy
// validationError will have the same reference as error
let validationError = error;
// ...
// following line will not trigger a rerender
setError(validationError);
Since error
and validationError
have the same reference, react
will asume no change has happened, thus it will cause bail out of a state update. If you work with complex types (like objects or arrays) in state you always need to create a new reference instead of adapting the previous one:
// this is an actual copy using the spread operator
// validationError will have different reference than error
let validationError = {...error};
// ...
// triggers rerender as expected
setError(validationError);
EDIT 2
I added a return value for validate
to use the updated validationError
object when calling validateForm
.
const validate = () => {
//..
let validationError = { ...error };
// ...
return validationError;
};
const onSubmit = e => {
// ...
const validationError = validate();
if (validateForm(validationError)) {
//...
}
};
See the updated code sandbox.
Upvotes: 1