Validation

mobx-schema-form provides facilities for form-wide validation (i.e. of required fields when you hit the Save button) in addition to intelligent real-time field-level validation with a variety of ways of defining the validations and formatting and displaying validation errors.

When validations run

All the built-in React-Toolbox widgets as well as any custom widgets created with asSchemaField() are 'Controlled' React components whose state is stored in model.data (numeric data-types also have a while-in-focus string representation state stored in SchemaField to enable correct entry of decimals and negative integers). Each such component raises onChange events to update this state on every change / keystroke.

Every such event is an opportunity to run validation on the updated value. However, for input fields such as text, validating and displaying error message when user has only started typing would result in poor user experience. Instead, for such fields we wait until the first blur event after some input to validate. But after that, any changes in this field are validated on every keystroke. Otherwise, validations are run on blur and only if necessary.

Note that when an input field of form type tel is configured with a mask, only its digits are passed to validation. For a North-American phone number mask such as (999) 999-9999, a pattern that would work is "[2-9][0-9]{2,2}[2-9][0-9]{6,6}".

However, every time onChange event updates model.data, plus when a widget is mounted and unmounted, onModelChange callback provided to MobxSchemaForm will be called, so custom asynchronous validation could be implemented there if desired.

In addition, whole-form validations (i.e. to mark required fields that are empty) is run (via validateAndSave()) whenever user clicks the SaveButton, or it can be run at any time (for example in FormStore beforeSave or afterRefresh callbacks) using validateForm(). Only currently mounted fields will be validated.

Note that starting with v1.6, if you mount multiple instances of MobxSchemaForm against the same FormStore model, whole-form validation will validate all fields from all mounted instances that use this model.

Running validation updates model.data with schema defaults

In addition to performing validations, validateForm() / validateField() update undefined or null data properties with defaults as defined in schema.default or formField.default (or the first value in schema.enum or formField.titleMap). Every time model.data is refreshed from the server, data properties will lose those defaults, so to restore them be sure to run validateForm() in the afterRefresh FormStore callback and to call model.refresh() in componentDidMount() of your form. Click here for more details.

How fields are validated

We use react-schema-form, which uses tv4 for schema-based validation as configured for each field in Data Property Schema (i.e. pattern, minimum, maximum, etc).

In addition, custom validation can be defined for each field, which is particularly necessary when validation needs to take into consideration the value of other data properties.

Here is an example of how you would create a validation that checks that the confirm password field has the same value as the password field. It defines a validator function under the keyword "sameAsPassword" and then references it in the validations array in the password_confirm field. You can also place the validator function right into the validations array but it's often best to keep the schema as a static json file which can't have functions in it.

import React from 'react';
import { observer } from 'mobx-react';
import { MobxSchemaForm } from 'mobx-schema-form';
import SaveButton from 'mobx-schema-form/lib/SaveButton';
import store from './myStore.js'; // a singleton instance of MobX FormStore

/* eslint-disable quotes, quote-props, comma-dangle */
// typically this would be in an external static json file
const schemaJson = {
  "schema": {
    "type": "object",
    "title": "Create Password",
    "properties": {
      "password": {
        "title": "Password",
        "description": "Must be at least 8 characters"
        "type": "string",
        "minLength": 8,
        "maxLength": 15
      },
      "password_confirm": {
        "title": "Confirm Password",
        "type": "string"
      }
    },
    "required": ["password", "password_confirm"]
  },
  "form": [
    {
      "key": "password",
      "type": "password"
    },
    {
      "key": "password_confirm",
      "type": "password",
      "validations": ["sameAsPassword"]
    }
  ],
  "defaults": {
    "validationMessage": {
      "default": "<%= title %> must be from <%= minlength %> to <%= maxlength %> characters in length",
      "302": "<%= title %> is required",
      "notSamePassword": "Confirm Password must be the same as Password"
    }
  }
};
/* eslint-enable */

// note that schema.minLength and maxLength are copied to formField.minlength and maxlength (lowercase!)

const options = {
  // you can either store validationMessage in global defaults like here
  // or in each form field that needs it but they do not merge!
  formDefaults: schemaJson.defaults,

  // define validator functions under keywords that will be referenced in validations above.
  validators: {
    sameAsPassword: (formField, model, value) => {
      if (model.data.password !== value) {
        // can return either object with code or error message string
        return { code: 'notSamePassword' };
      }
      return null;
    },
  },
};

@observer class MyForm extends React.Component {
  render() {
    return (
      <form>
        <MobxSchemaForm
          schema={schemaJson.schema}
          form={schemaJson.form}
          model={store}
          options={options}
        />
        <SaveButton
          model={store}
          options={{ allowCreate: true, saveAll: true }}
          label="Save"
          disabled={!store.status.canSave}
          type="submit"
        />
      </form>
    );
  }
}

Server-side or other validation

Server-side validation can be performed when the data is saved to the server and server can respond with error messages for each field or with objects that reference codes for which validationMessage is defined like in the example above.

Finally, you can perform other kinds of validation and as long as you place the resulting error message or object in tv4 validation error format (i.e. with code and optional message) into model.dataErrors, the corresponding widget will immediately render as being in error and show the validation error message.

Validation messages

tv4 has built-in validation error messages but they are not very user-friendly. You can optionally specify the validation error messages to be used on each field on a per-tv4-error-code basis plus a catch-all "default" message in the validationMessage property of Form Field Metadata or in the options.formDefaults object passed to MobxSchemaForm like in the example above. The messages themselves are parsed as a template with lodash.template in whatever format you have configured in your app with lodash.templateSettings.

Instead of a template string, you can also use a function, which will receive the same context object as the lodash template, and should return an error message, but defining this in Form Field Metadata requires either post-processing the form object from the json file or not storing the schema in a .json file.

Datepickers should use data properties of type object. In prior versions of mobx-schema-form, they did not return the usual tv4 302 code for required fields, so the "default" fall-back had to be used for them, but now either one can be used.

Last updated