I have just started using react and created a basic form which consists 3 fields (username, password and a submit button). I am only using reducers and state within this component and while testing out my validation I picked up that the button enabled property as a reverse reaction if I enter a correct email, backspace the '@' character and enter the '@' character back. What should happen is if the entered email doesn't include an '@' I set formIsValid property to false and if it does then I set it to true which enable the login button. I have a hunch that maybe the component isn't re-rendering but I have no idea on how to prove this nor fix this.
Screenshot of validation fails but button is still enabled
adding the '@' in email now disables button
This only occurs if I type something and then start backspacing and re-entering into the field, If I go in the order of filling in the email and then filling in the password as needed then is works as it should
Component :
import React,
// eslint-disable-next-line
{ useEffect, useState, useReducer } from 'react';
import Card from '../UI/Card/Card';
import classes from './Login.module.css';
import Button from '../UI/Button/Button';
const emailReducer = (state, action) => {
if(action.type === 'USER_INPUT') {
return {value: action.val, isValid: action.val.includes('@') };
}
if (action.type === 'INPUT_BLUR') {
return {value: state.value, isValid: state.value.includes('@') };
}
return { value: '', isValid: false }
};
const passwordReducer = (state, action) => {
if(action.type === 'USER_INPUT') {
return {value: action.val, isValid: action.val.trim().length > 6}
}
if(action.type === 'INPUT_BLUR') {
return {value: state.value, isValid: state.value.trim().length > 6}
}
};
const Login = (props) => {
// const [enteredEmail, setEnteredEmail] = useState('');
// const [emailIsValid, setEmailIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {value: '', isValid: null});
const [passwordState, dispatchPassword] = useReducer(passwordReducer, {value: '', isValid: null})
const emailChangeHandler = (event) => {
dispatchEmail({type: 'USER_INPUT', val: event.target.value});
setFormIsValid(
emailState.isValid && passwordState.isValid
);
};
// useEffect(() => {
// const identifier = setTimeout(() => {
// setFormIsValid(
// enteredEmail.includes('@') && enteredPassword.trim().length > 6
// );
// }, 500);
// return () => {
// console.log('CLEANUP');
// clearTimeout(identifier);
// };
// }, [enteredEmail, enteredPassword]);
const passwordChangeHandler = (event) => {
dispatchPassword({type: 'USER_INPUT', val: event.target.value});
setFormIsValid(
passwordState.isValid && emailState.isValid
);
};
const validateEmailHandler = () => {
dispatchEmail({type: 'INPUT_BLUR'});
};
const validatePasswordHandler = () => {
dispatchPassword({type: 'INPUT_BLUR'})
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, passwordState.value);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${emailState.isValid === false ? classes.invalid : ''
}`}
>
<label htmlFor="email">E-Mail</label>
<input
type="email"
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${passwordState.isValid === false ? classes.invalid : ''
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={passwordState.value}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
The above code has 2 reducers, 1 for email state and 1 for password state
stateObject:
{
value: '',
isValid: false
}
I am using the useState
hook to only manage if the entire form is valid which is basically determined by emailState.IsValid && passwordState.IsValid
Also I am listening for onchange events on both input fields which executes my reducer functions for their respective state
I tried removing the expression that uses the reducer state and went with
event.target.value.includes('@') && passwordState.isValid
thinking that maybe the reducer state is not updating in time before my component renders but that did not work. Any help pointing me to where the problem is and what is causing this would be really appreciated, thanks in advance