I have that simple code:
const Login = (): JSX.Element => {
const {errors}= useAppSelector(state => state.errorsReducer)
const dispatch = useAppDispatch();
const navigate = useNavigate();
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const onSubmit = (e: FormEvent) => {
e.preventDefault();
};
const login = () => {
dispatch(errorsSlice.actions.clearErrors());
console.log(errors);
const userData = {
email: email,
password: password
}
if(!userData.email || !userData.password){
dispatch(errorsSlice.actions.addError('Email or password is undefined'));
}
if(errors.length === 0){
axios
.post('http://localhost:7000/auth/login', userData)
.then(response =>{
const decodedToken: IUser = jwt_decode(response.data.token);
localStorage.setItem('userData', JSON.stringify(decodedToken));
localStorage.setItem('token', JSON.stringify(response.data.token));
navigate('/')
})
.catch(e => {
const errorMessage = e?.response?.data?.message
dispatch(errorsSlice.actions.addError(errorMessage));
});
}
console.log(errors);
}
return (
<main className="form-signin m-auto" style={{height: 600}}>
<div className='container h-100 d-flex align-items-center justify-content-center'>
<form onSubmit={(e) => onSubmit(e)} style={{width: 400}}>
<h1 className="h3 mb-3 fw-normal">Auth</h1>
<div className="form-floating">
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} className="form-control" id="floatingInput" placeholder="name@example.com"/>
<label htmlFor="floatingInput">Email</label>
</div>
<div className="mt-2 form-floating">
<input type="password" className="form-control" value={password} onChange={(e) => setPassword(e.target.value)} id="floatingPassword" placeholder="Password"/>
<label htmlFor="floatingPassword">Password</label>
</div>
<div className="checkbox mt-2 mb-3">
<label>
Or <Link to='/registration'>click that</Link> to registration.
</label>
</div>
<button className="w-100 btn btn-lg btn-primary" onClick={() => login()} type="submit">Login</button>
</form>
</div>
<>
{errors.map((error) => {
return <MyToast
text = {error}
color = {'bg-danger'}
show = {true}
key = {error}
/>
})}
</>
</main>
)
}
On click Login button call function login() which in case no errors send request on server. I testing requests with errors. That works on first time, dispatch set error to store and all works good, but on second click should work dispatch(errorsSlice.actions.clearErrors()), but his work with delay and when my function go to expression if(errors.length === 0), then expression return false, because errors.length = 1, although dispatch(errorsSlice.actions.clearErrors()) should have worked. What me need do, to that code works true?
The selected errors
state value is closed over in login
callback scope, it will never be a different value in the callback. You can't dispatch actions to update the state and expect the closed over values to change.
I suspect you are really just wanting to validate the inputs and dispatch the addError
action if there are issues, otherwise continue with the login flow. I suggest a small refactor to compute and check local errors to avoid the issue of the stale closure over the selected errors
state.
Example:
const Login = (): JSX.Element => {
const { errors } = useAppSelector(state => state.errorsReducer)
const dispatch = useAppDispatch();
const navigate = useNavigate();
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
dispatch(errorsSlice.actions.clearErrors());
// check for field data issue, early return
if (!email || !password){
dispatch(errorsSlice.actions.addError('Email or password is undefined'));
return;
}
// no field data issues, try authenticating
try {
const userData = { email, password };
const response = await axios.post(
"http://localhost:7000/auth/login",
userData
);
const decodedToken: IUser = jwt_decode(response.data.token);
localStorage.setItem('userData', JSON.stringify(decodedToken));
localStorage.setItem('token', JSON.stringify(response.data.token));
navigate('/');
} catch(e) {
const errorMessage = e?.response?.data?.message
dispatch(errorsSlice.actions.addError(errorMessage));
};
}
return (
<main className="form-signin m-auto" style={{ height: 600 }}>
<div className="container h-100 d-flex align-items-center justify-content-center">
<form onSubmit={onSubmit} style={{width: 400}}>
<h1 className="h3 mb-3 fw-normal">Auth</h1>
<div className="form-floating">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="form-control"
id="floatingInput"
placeholder="name@example.com"
/>
<label htmlFor="floatingInput">Email</label>
</div>
<div className="mt-2 form-floating">
<input
type="password"
className="form-control"
value={password}
onChange={(e) => setPassword(e.target.value)}
id="floatingPassword"
placeholder="Password"
/>
<label htmlFor="floatingPassword">Password</label>
</div>
<div className="checkbox mt-2 mb-3">
<label>
Or <Link to='/registration'>click that</Link> to registration.
</label>
</div>
<button className="w-100 btn btn-lg btn-primary" type="submit">
Login
</button>
</form>
</div>
<>
{errors.map((error) => (
<MyToast
key={error}
color="bg-danger"
show
text={error}
/>
))}
</>
</main>
)
}