mobx-react-form
Version:
Reactive MobX Form State Management
119 lines (101 loc) • 3.26 kB
text/typescript
import _ from "lodash";
import FieldInterface from "src/models/FieldInterface";
import FormInterface from "src/models/FormInterface";
import {
ValidationPlugin,
ValidationPluginConfig,
ValidationPluginConstructor,
ValidationPluginInterface,
} from "../models/ValidatorInterface";
function isPromise(obj: any): obj is Promise<any> {
return (
!!obj &&
typeof obj.then === "function" &&
(typeof obj === "object" || typeof obj === "function")
);
}
class SVK<TValidator = any> implements ValidationPluginInterface<TValidator> {
promises: Promise<any>[];
config: ValidationPluginConfig<TValidator>;
state: any;
extend?: (args: { validator: TValidator; form: FormInterface }) => void;
validator: any;
schema: any;
constructor({
config,
state = null,
promises = [],
}: ValidationPluginConstructor<TValidator>) {
this.state = state;
this.promises = promises;
this.config = config;
this.extend = config?.extend;
this.schema = config.schema;
this.initValidator();
}
extendOptions(options: any = {}) {
return {
...options,
errorDataPath: "property",
allErrors: true,
coerceTypes: true,
v5: true,
};
}
initValidator(): void {
const AJV = this.config.package as any;
const validatorInstance = new AJV(this.extendOptions(this.config.options));
if (typeof this.extend === "function") {
this.extend({
form: this.state.form,
validator: validatorInstance,
});
}
this.validator = validatorInstance.compile(this.schema);
}
validate(field: FieldInterface): void {
const result = this.validator(field.state.form.validatedValues);
if (isPromise(result)) {
const $p = result
.then(() => field.setValidationAsyncData(true))
.catch((err) => err && this.handleAsyncError(field, err.errors))
.then(() => this.executeAsyncValidation(field));
this.promises.push($p);
return;
}
this.handleSyncError(field, this.validator.errors);
}
handleSyncError(field: FieldInterface, errors: any[]): void {
const fieldError = this.findError(field.path, errors);
if (!fieldError) return;
const message = `${field.label} ${fieldError.message}`;
field.invalidate(message, false);
}
handleAsyncError(field: FieldInterface, errors: any[]): void {
const fieldError = this.findError(field.path, errors);
if (!fieldError) return;
const message = `${field.label} ${fieldError.message}`;
field.setValidationAsyncData(false, message);
}
findError(path: string, errors: any[]): any {
return _.find(errors, ({ dataPath }) => {
let $dataPath;
$dataPath = _.trimStart(dataPath, ".");
$dataPath = _.replace($dataPath, "]", "");
$dataPath = _.replace($dataPath, "[", ".");
return _.includes($dataPath, path);
});
}
executeAsyncValidation(field: FieldInterface): void {
const asyncData = field.validationAsyncData;
if (asyncData.valid === false) {
field.invalidate(asyncData.message, false, true);
}
}
}
export default <TValidator = any>(
config?: ValidationPluginConfig<TValidator>
): ValidationPlugin<TValidator> => ({
class: SVK<TValidator>,
config,
});