Docs
Examples
Async validation

Asynchronous validation

Most common async validation is a network request checking with the backend if the current value is available, like username or email, but it can be anything.

Building upon the previous example, we are adding an async validator to our form configuration object. Async validators are Promises that resolve with either an Error object or undefined.

Returned validations object contains validation state information for your form fields. In the below example, alongside rendering an error message, it is used to indicate a loading state during "username" validation.

Also, notice that our password field gets validated with Regex.

import { useForm } from "@formeus/react"
 
function SignIn({ someState }) {
  const { values, update, submit } = useForm({
    initial: {
      username: "",
      password: "",
    },
    validators: {
      username: ({ username }) =>
        username.length == 0
          ? new Error("Must contain at least 1 char.")
          : undefined,
      password: ({ password }) =>
        /^((?=\S*?[A-Z])(?=\S*?[a-z])(?=\S*?[0-9]).{5,})\S/.test(password)
          ? undefined
          : new Error(
              "1 uppercase, 1 lowercase, 1 number and at least 6 chars."
            ),
    },
    asyncValidators: {
      username: ({ username }, signal) => {
        // username is already client side validated here
        return api
          .checkUsernameAvailable(username, signal)
          .then((isAvailable) =>
            isAvailable ? undefined : new Error("username not available")
          )
      },
    },
    onSubmitForm: ({ username, password }, signal, meta) => {
      // someState in the line below will have an up to date value,
      // even if it was undefined on initial render
      const { someState } = meta
 
      api.signIn(username, password)
    },
    meta: {
      someState,
    },
  })
 
  return (
    <>
      <input
        value={values.username}
        onChange={(e) => update("username", e.target.value)}
      />
      <label>{validations.username.error?.message}</label>
      {validations.username.validating && <span>Loading...</span>}
 
      <input
        value={values.password}
        onChange={(e) => update("password", e.target.value)}
      />
      <label>{validations.password.error?.message}</label>
 
      <button onClick={() => submit()}>Submit</button>
    </>
  )
}
ℹ️

validators always run before asyncValidators, preventing unecessary requests for something that can be checked on the client. (like an empty field).

UX considerations

To provide the best user experience when executing an asynchronous validation, make use of the returned validations object in conjuction with isValid and isValidating flags to achieve the desired form behaviour.

Most obvious usage of isValid flag is to control the button state, which can perhaps remain disabled as long as the form isValid = false.

With that setup, user can't trigger the validation process with the submit method so make use of the autoValidate form configuration option to automatically run validation when the field is updated.