antd - Form input dynamic validation

Viewed 1099

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:

Edit unruffled-darkness-vjo00

1 Answers

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:

Edit unruffled-darkness-vjo00

Related