4. Validation

In the final-form library it is possible to add field level and form level validations. jarb-final-form automatically injects the field level validation into your forms. But it also allows you to define custom field level validation as well.

It supports both synchronous and asynchronous validation.

4.1 Synchronous validation

Regular old synchronous validators can be added via the JarbField’s validators property. It is an array of functions were each function is a validator.

For example we can define the following custom async validators:

/* validation.ts */

/* Validates based on a single field */
export function isEmail(email: string | undefined): string | undefined {
  if (email && email.indexOf('@') === -1) {
    return 'Not an email!';
  } else {
    return undefined;
  }
}

/* Validates based on a form */
export function isPasswordTheSame(
  _: string | undefined,
  userForm: any
): string | undefined {
  if (userForm.password === userForm.confirmPassword) {
    return undefined;
  }

  return 'Passwords do not match!';
}

And use them inside of a UserForm.

import React from 'react';
import { Form } from 'react-final-form';
import { JarbField } from '@42.nl/jarb-final-form';

import { isEmail, isPasswordTheSame } from './validation';

/*
  Define the array outside of the component so that the same
  array is used for every UserForm.
*/
const emailValidators = [isEmail];

const confirmPasswordValidators = [isPasswordTheSame];

function UserForm() {
  return (
    <Form
      onSubmit={onSubmit}
      initialValues={initialValues}
      render={({ handleSubmit }) => (
        <form onSubmit={handleSubmit}>
          <JarbField
            name="email"
            jarb={{ validator: 'User.email', label: "email" }}
            validators={emailValidators}
            component="input"
            type="text"
          />

           <JarbField
            name="password"
            jarb={{ validator: 'User.password', label: "password" }}
            component="input"
            type="password"
          />

          <JarbField
            name="confirmPassword"
            jarb={{ validator: 'User.password', label: "confirmPassword" }}
            component="input"
            type="password"
            validators={confirmPasswordValidators}
          />
        </form>
      )}
    />
  );
}

4.2 Asynchronous validation

Asynchronous validators can be added via the JarbField’s asyncValidators property. It is an array of functions were each function is a validator, which returns a promise.

The asyncValidators will only run after the synchronous validator have run and have considered the field valid.

The asyncValidators are also debounced by default at 200 milliseconds. Via the asyncValidatorsDebounce this can be changed per field.

/* validation.ts */

export async function isEmailUnique(
  email: string | undefined,
  allValues: object,
  meta?: FieldState<string>
): Promise<string | undefined> {
  /* Only run when the field is active */
  if (meta && !meta.active) {
    return;
  }

  /* 
    If it has no value it is considered valid, the required 
    validator should handle this case!
  */
  if (!email) {
    return Promise.resolve(undefined);
  }

  /* Sends request to back-end */
  const isTaken = await User.isEmailTaken(email);

  return isTaken 
    ? `The ${email} is already used.` 
    : Promise.resolve(undefined);
}

And use them inside of a user form.

import React from 'react';
import { Form } from 'react-final-form';
import { JarbField } from '@42.nl/jarb-final-form';

import { isEmailUnique } from './validation';

/*
  Define the array outside of the component so that the same
  array is used for every UserForm.
*/
const asyncEmailValidators = [isEmailUnique];

function UserForm() {
  return (
    <Form
      onSubmit={onSubmit}
      initialValues={initialValues}
      render={({ handleSubmit }) => (
        <form onSubmit={handleSubmit}>
          <JarbField
            name="email"
            jarb={{ validator: 'User.email', label: "email" }}
            asyncEmailValidators={emailValidators}
            /* Override the default 200 milliseconds */
            asyncValidatorsDebounce={1000}
            component="input"
            type="text"
          />
        </form>
      )}
    />
  );
}