@open-formulieren/formio-builder
Version:
An opinionated Formio webform builder for Open Forms
131 lines (130 loc) • 5.62 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorList = exports.useValidationErrors = exports.getErrorNames = exports.useErrorChecker = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const formik_1 = require("formik");
/**
* Provides tools to introspect validation errors for Formik-based forms.
*/
function useErrorChecker() {
const { errors } = (0, formik_1.useFormikContext)();
const pathsWithErrors = getErrorNames(errors);
/**
* Check if there currently are any validation errors for the provided (partial) paths.
*
* Example usage:
*
* hasAnyError(errors, 'key', 'defaultValue', 'validate.maxLength')
*
* @param errors - The Formik validation errors structure.
*/
function hasAnyError(...paths) {
// if no paths are provided, there is no filter, so return if there is *any*
// validation error.
if (!paths.length) {
return pathsWithErrors.length > 0;
}
// otherwise, check if there is overlap between the array of errored paths and
// specified paths.
// In the future, we should be able to use Set.prototype.intersection for this.
return paths.some(p => pathsWithErrors.includes(p));
}
return {
hasAnyError,
};
}
exports.useErrorChecker = useErrorChecker;
/**
* Recursively look up the names of fields that have errors, and join them using
* path-syntax.
*
* Example:
*
* getErrorNames({foo: {bar: 'an error!'}})
* // ['foo', ['foo.bar']]
*
* @private
*/
function getErrorNames(errors) {
const names = [];
// Iterating over objects is surprisingly hard/annoying in TS due to JS' quirks, see
// https://effectivetypescript.com/2020/05/26/iterate-objects/
let k;
for (k in errors) {
const error = errors[k];
// if there is nothing to display (error is undefined or empty string), then the
// field may not be marked as having errors. This function is primarily used when
// determining which edit form tabs/groups of fields have errors to draw the
// attention of users.
if (!error)
continue;
switch (typeof error) {
case 'string': {
names.push(k);
break;
}
case 'object': {
let nestedNames = [];
if (Array.isArray(error)) {
error.forEach((item, index) => {
// same condition as earlier
if (!item)
return;
switch (typeof item) {
// a single error, for the entire object in the array. Could be because of
// validation after each field was individually validated.
case 'string': {
nestedNames.push(index.toString());
break;
}
case 'object': {
const deepNested = getErrorNames(item).map(n => `${index}.${n}`);
if (deepNested.length) {
nestedNames.push(index.toString(), ...deepNested);
}
break;
}
default:
throw new Error('Unexpected item type');
}
});
}
else {
nestedNames = getErrorNames(error);
}
if (nestedNames.length) {
// include the parent to get all nodes from root -> error leaf node in the
// list of errored fields
const prefixed = nestedNames.map(n => `${k}.${n}`);
names.push(k, ...prefixed);
}
break;
}
default:
throw new Error('Unexpected error type');
}
}
// Satisfying typescript seems to be quite a challenge here. There's definitely some
// blame to JS quirks (see https://effectivetypescript.com/2020/05/26/iterate-objects/
// for more background), but the combination of our Paths enum with (inferred)
// string keys (from keyof Object + FormikErrors) doesn't place nice together either.
//
// There is also an aspect on our side - the Paths<> generic currently is *not* able
// to figure out a naming pattern for arrays/lists (e.g. something like `foo[].bar`),
// while we *do* return error names like `foo.1.bar` in this implementation.
return names;
}
exports.getErrorNames = getErrorNames;
const useValidationErrors = (name) => {
const { getFieldMeta } = (0, formik_1.useFormikContext)();
// FIXME: inferred type says string, but for nested objects this can be an object itself!
const { error } = name ? getFieldMeta(name) : { error: '' };
const errors = name && error && typeof error === 'string' ? [error] : [];
return {
hasErrors: errors.length > 0,
errors,
};
};
exports.useValidationErrors = useValidationErrors;
const ErrorList = ({ errors }) => ((0, jsx_runtime_1.jsx)("div", Object.assign({ className: "formio-errors invalid-feedback" }, { children: errors.map((error, index) => ((0, jsx_runtime_1.jsx)("div", Object.assign({ className: "form-text error" }, { children: error }), index))) })));
exports.ErrorList = ErrorList;