Reputation: 1432
In my project, I am using antd Form
in the login screen. If the user hasn't provided email or if the email is invalid, I am handling that using the rules
attribute that antd's Form.Item
supports.
Here is what I wanted to achieve:
I also wanted to handle another error which is 'No account associated with email'. After the user click on the login button, there is a back-end API call. When the backend returns this error in the response, then UI should show this error message under the email input.
To achieve this, I am storing this error message in a state variable 'emailError'
and I'm using this in the 'rules'
attribute of the Form.Item
as follows:
function Login() {
const [loginForm] = Form.useForm();
const [emailError, setEmailError] = useState({ enable: false, helpText: "" });
const emailErrorRef = useRef(emailError);
const onLoginFormSubmit = async (values) => {
try {
let response = await loginRequest(values);
console.log("Login -> onLoginFormSubmit -> response", response);
// ...some logic
} catch (err) {
/*
err.response.data = {
code: 'ERR_LOGIN_NO_ACCOUNT_WITH_EMAIL',
detail: 'No account associated with this email'
}
*/
let errCode = err.response.data.code;
let errMessage = err.response.data.detail;
// If the API fails with the below error, then update the state variable.
if (errCode === "ERR_LOGIN_NO_ACCOUNT_WITH_EMAIL") {
console.log(
"Login -> update emailError -> ERR_LOGIN_NO_ACCOUNT_WITH_EMAIL"
);
setEmailError({
enable: true,
helpText: errMessage
});
}
}
};
useEffect(() => {
console.log("Login -> updated emailError", emailError);
emailErrorRef.current = emailError;
}, [emailError]);
return (
<div
style={{
position: "fixed",
width: 400,
height: 200,
top: "calc(50% - 100px)",
left: "calc(50% - 200px)"
}}
>
<Form form={loginForm} onFinish={onLoginFormSubmit}>
<Form.Item
name="email"
rules={[
{
type: "email",
message: "Please provide a valid email!"
},
{ required: true, message: "Please provide your email!" },
() => ({
validator() {
console.log(
"Inside email validator! -> emailError",
emailError
);
if (emailError.enable) {
return Promise.reject(emailError.helpText);
}
return Promise.resolve();
}
})
]}
>
<Input autoFocus placeholder="Email" prefix={<EmailIcon />} />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: "Please provide your password!" }]}
>
<Input.Password placeholder="Password" prefix={<PasswordIcon />} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Login
</Button>
</Form.Item>
</Form>
</div>
);
}
From what I can understand based on the logs, I think that the Form is not being re-rendered when I am updating the state variable 'emailError'
after receiving the response from async API call.
UPDATE: I am also logging the state variable 'emailError'
in the HTML. The change is state variable is reflected in the UI. So, I think that the re-rendering is working fine. But, during the re-render, the rules
are not being validated..
I have tried to use 'emailErrorRef.current'
instead of 'emailError'
in the validator()
but this didn't work.
What am I missing here? Please help fix this!
Here is a codesandbox which replicates this:
Upvotes: 2
Views: 6724
Reputation: 1
import "../css/login.css";
import { LockOutlined, UserOutlined } from "@ant-design/icons";
import { Button, Checkbox, Form, Input, Card, Space, Image } from "antd";
import logo from "../images/logo.png";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
function LoginSample() {
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [emailError, setEmailError] = useState("");
const [password, setPassword] = useState("");
const [passwordError, setPasswordError] = useState("");
const [successMsg, setSuccessMsg] = useState("");
const handleEmailChange = (e) => {
setSuccessMsg("");
setEmailError("");
setEmail(e.target.value);
};
const handlePasswordChange = (e) => {
setSuccessMsg("");
setPasswordError("");
setPassword(e.target.value);
};
const handleFormSubmit = (e) => {
//empty email
if (email !== "") {
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
//type of email
if (emailRegex.test(email)) {
setEmailError("");
//correct email or not
if (email == "[email protected]") {
setEmailError("");
if (password == "admin") {
navigate("/dashboard");
} else {
setPasswordError("password incorect");
}
} else {
setEmailError("email incorrect");
}
} else {
setEmailError("Invalid Email");
}
} else {
setEmailError("Email Required");
}
if (password !== "") {
} else {
setPasswordError("Password Required");
}
};
return (
<Space
direction="horizontal"
style={{ width: "100%", justifyContent: "center", marginTop: "150px" }}
>
<Card
style={{
width: 350,
margin: "20px",
borderRadius: "20px",
overflow: "hidden",
}}
>
<Space
direction="horizontal"
style={{ width: "100%", justifyContent: "center" }}
>
<Image width={200} src={logo} preview={false} />
</Space>
<Form
name="normal_login"
className="login-form"
initialValues={{
remember: true,
}}
>
<Form.Item>
<Input
type="email"
onChange={handleEmailChange}
value={email}
prefix={<UserOutlined className="site-form-item-icon" />}
placeholder="Email"
/>
{emailError && <Space className="error-msg">{emailError}</Space>}
</Form.Item>
<Form.Item>
<Input
type="password"
onChange={handlePasswordChange}
value={password}
prefix={<LockOutlined className="site-form-item-icon" />}
placeholder="Password"
/>
{passwordError && (
<Space className="error-msg">{passwordError}</Space>
)}
</Form.Item>
<Form.Item>
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>Remember me</Checkbox>
</Form.Item>
<a className="login-form-forgot" href="">
Forgot password
</a>
</Form.Item>
<Form.Item>
<Button
type="primary"
onClick={() => handleFormSubmit()}
className="login-form-button"
>
Log in
</Button>
<Space
direction="horizontal"
style={{ width: "100%", justifyContent: "center" }}
>
OR
</Space>
<Space
direction="horizontal"
style={{ width: "100%", justifyContent: "center" }}
>
<a href="">Register for new account</a>
</Space>
</Form.Item>
</Form>
</Card>
</Space>
);
}
export default LoginSample;
Upvotes: 0
Reputation: 6554
Your validation calls when you apply a change on one of the fields and when you submit the form, so when you call your api and get the error, you'r validation won't call until apply a change on form or submit it again, a solution could be calling validation programatically by using validateFields
on antd form. like this:
useEffect(() => {
if(emailError.enable){
loginForm.validateFields();
}
}, [emailError]);
...
<Form form={loginForm}
onChange={()=> {
if(emailError.enable)
setEmailError({enable:false, helpText:''})
}}
...></Form>
Here is the full example:
Upvotes: 2