Skip to content

Validators

Michael Brandt edited this page Jun 15, 2024 · 10 revisions

On this page

Validators

Validators are functions that takes a value to validate and returns undefined if there is no error and an error message (string) if there is an error.

You can use the builtin validators from simple-form or your own or a combination.

How to use the builtin validators

The builtin validators are available with the useValidationRules hook like.

const { required, maxLength } = useValidationRules();

The builtin validators are functions that returns a Validator<T> which in itself is a function, so when you use a validator in a form definition, you must invoke it.

const fd = useFormDefinition<FormFields>({
  fields: {
    name: {
      validators: [required(), maxLength(50)]
    },
    age: {
      validators: [required()]
    }
  }
});

Some validators takes some configuration parameters, but they all take an optional errorMessage as the last parameter. If you call them without the errorMessage parameter, they use the error messages from simple-form, but you have the opportunity to override it with a custom message.

const fd = useFormDefinition<FormFields>({
  fields: {
    name: {
-      validators: [required(), maxLength(50)]
+      validators: [required("Please tell us your name."), maxLength(50, "Oops no more than 50 characters.)]
    },
    age: {
-      validators: [required()]
+      validators: [required("Enter your age.")]
    }
  }
});

The alwaysValid validator

simple-form exposes an alwaysValid validator which always returns undefined meaning no error.

This can come in handy when constructing an array of validators dynamically.

Assume our component has a prop telling if age is a required field.

type Props = {
  ageRequired: boolean;
};

const PersonForm = (props: Props) => {
  const { ageRequired } = props;
  //... the rest of the component
}

The age field is defined like this so far:

  age: {
    validators: [required(), min(3)]
  },

We only want to include the required validator if ageRequired is true.

  age: {
-   validators: [required(), min(3)]
+   validators: ageRequired ? [required(), min(3), max(125)] : [min(3), max(125)]
  },

This works, but as you can see we repeat the min(3) validator because we want this regardless of ageRequired. If we had more validators we could easily forget one in the two situations. Instead we can use alwaysValid which makes it easier to create the array.

  age: {
-   validators: [required(), min(3)]
+   validators: [ageRequired ? required() : alwaysValid, min(3), max(125)]
  },

Now we only specify the other validators one time.

Note alwaysValid is not a function like the rest of the validators. This is because the other validators are functions so they can take a custom error message, but as alwaysValid never returns an error, it can be just a constant.

Custom validation rules

If the validators from simple-form aren't sufficient, you can easily create your own.

Your validator must be a Validator<T> where T is the type it is validating - this ensures you can't by mistake apply it to a field of another type than T.

The validator should return undefined if there are no errors and an error message otherwise. The validator should not return any errors if the value is null (because this should be handled by the required validator).

Say you only want to allow odd numbers for a field.

const odd: Validator<number> = value => (hasValue(value) && value % 2 !== 0 ? "Must be an odd number" : undefined);

You can then use it like this:

mustBeOdd: {
  validators: [odd]
}

If you are exporting the validator and intent to use it other places, you should create it like the builtin validators, taking a parameter for an optional custom error message.

const odd =
  (errorMessage?: string): Validator<number> =>
  value =>
    hasValue(value) && value % 2 !== 0 ? errorMessage ?? "Must be an odd number" : undefined;

And then invoke it when you use it:

mustBeOdd: {
  validators: [odd()]
}

Special note on required validator

Have you noticed that you don't have to tell the form components when the value is required other than using the required validator? How does simple-form find out that among the array of validators (which are functions) one of them is the required validator? It does so by looking at the name of the function. This is the reason the required validator is implemented using function syntax and not arrow syntax.

const required = <T>(errorMessage?: string): Validator<T> =>
  // Must be written like this because the name of the function returned must start with "required".
  function required(value) {
    return hasValue(value) ? undefined : errorMessage ?? "Value is required";
  };

You can create your own validators for required (for instance I use a special version for usage with Facebook's draft editor). The only requirement is that you implement it as above and that your function starts with "required" in the name.