@matthew.ngo/reform
Version:
A flexible and powerful React form management library with advanced validation, state observation, and multi-group support
1,467 lines (1,450 loc) • 171 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var get = _interopDefault(require('lodash/get'));
var reactHookForm = require('react-hook-form');
var yup = require('@hookform/resolvers/yup');
var yup$1 = require('yup');
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function asyncGeneratorStep(n, t, e, r, o, a, c) {
try {
var i = n[a](c),
u = i.value;
} catch (n) {
return void e(n);
}
i.done ? t(u) : Promise.resolve(u).then(r, o);
}
function _asyncToGenerator(n) {
return function () {
var t = this,
e = arguments;
return new Promise(function (r, o) {
var a = n.apply(t, e);
function _next(n) {
asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
}
function _throw(n) {
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
}
_next(void 0);
});
};
}
function _createForOfIteratorHelperLoose(r, e) {
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (t) return (t = t.call(r)).next.bind(t);
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
t && (r = t);
var o = 0;
return function () {
return o >= r.length ? {
done: !0
} : {
done: !1,
value: r[o++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function _inheritsLoose(t, o) {
t.prototype = Object.create(o.prototype), t.prototype.constructor = t, _setPrototypeOf(t, o);
}
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (e.includes(n)) continue;
t[n] = r[n];
}
return t;
}
function _regeneratorRuntime() {
_regeneratorRuntime = function () {
return e;
};
var t,
e = {},
r = Object.prototype,
n = r.hasOwnProperty,
o = Object.defineProperty || function (t, e, r) {
t[e] = r.value;
},
i = "function" == typeof Symbol ? Symbol : {},
a = i.iterator || "@@iterator",
c = i.asyncIterator || "@@asyncIterator",
u = i.toStringTag || "@@toStringTag";
function define(t, e, r) {
return Object.defineProperty(t, e, {
value: r,
enumerable: !0,
configurable: !0,
writable: !0
}), t[e];
}
try {
define({}, "");
} catch (t) {
define = function (t, e, r) {
return t[e] = r;
};
}
function wrap(t, e, r, n) {
var i = e && e.prototype instanceof Generator ? e : Generator,
a = Object.create(i.prototype),
c = new Context(n || []);
return o(a, "_invoke", {
value: makeInvokeMethod(t, r, c)
}), a;
}
function tryCatch(t, e, r) {
try {
return {
type: "normal",
arg: t.call(e, r)
};
} catch (t) {
return {
type: "throw",
arg: t
};
}
}
e.wrap = wrap;
var h = "suspendedStart",
l = "suspendedYield",
f = "executing",
s = "completed",
y = {};
function Generator() {}
function GeneratorFunction() {}
function GeneratorFunctionPrototype() {}
var p = {};
define(p, a, function () {
return this;
});
var d = Object.getPrototypeOf,
v = d && d(d(values([])));
v && v !== r && n.call(v, a) && (p = v);
var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p);
function defineIteratorMethods(t) {
["next", "throw", "return"].forEach(function (e) {
define(t, e, function (t) {
return this._invoke(e, t);
});
});
}
function AsyncIterator(t, e) {
function invoke(r, o, i, a) {
var c = tryCatch(t[r], t, o);
if ("throw" !== c.type) {
var u = c.arg,
h = u.value;
return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) {
invoke("next", t, i, a);
}, function (t) {
invoke("throw", t, i, a);
}) : e.resolve(h).then(function (t) {
u.value = t, i(u);
}, function (t) {
return invoke("throw", t, i, a);
});
}
a(c.arg);
}
var r;
o(this, "_invoke", {
value: function (t, n) {
function callInvokeWithMethodAndArg() {
return new e(function (e, r) {
invoke(t, n, e, r);
});
}
return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg();
}
});
}
function makeInvokeMethod(e, r, n) {
var o = h;
return function (i, a) {
if (o === f) throw Error("Generator is already running");
if (o === s) {
if ("throw" === i) throw a;
return {
value: t,
done: !0
};
}
for (n.method = i, n.arg = a;;) {
var c = n.delegate;
if (c) {
var u = maybeInvokeDelegate(c, n);
if (u) {
if (u === y) continue;
return u;
}
}
if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) {
if (o === h) throw o = s, n.arg;
n.dispatchException(n.arg);
} else "return" === n.method && n.abrupt("return", n.arg);
o = f;
var p = tryCatch(e, r, n);
if ("normal" === p.type) {
if (o = n.done ? s : l, p.arg === y) continue;
return {
value: p.arg,
done: n.done
};
}
"throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg);
}
};
}
function maybeInvokeDelegate(e, r) {
var n = r.method,
o = e.iterator[n];
if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y;
var i = tryCatch(o, e.iterator, r.arg);
if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y;
var a = i.arg;
return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y);
}
function pushTryEntry(t) {
var e = {
tryLoc: t[0]
};
1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e);
}
function resetTryEntry(t) {
var e = t.completion || {};
e.type = "normal", delete e.arg, t.completion = e;
}
function Context(t) {
this.tryEntries = [{
tryLoc: "root"
}], t.forEach(pushTryEntry, this), this.reset(!0);
}
function values(e) {
if (e || "" === e) {
var r = e[a];
if (r) return r.call(e);
if ("function" == typeof e.next) return e;
if (!isNaN(e.length)) {
var o = -1,
i = function next() {
for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next;
return next.value = t, next.done = !0, next;
};
return i.next = i;
}
}
throw new TypeError(typeof e + " is not iterable");
}
return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", {
value: GeneratorFunctionPrototype,
configurable: !0
}), o(GeneratorFunctionPrototype, "constructor", {
value: GeneratorFunction,
configurable: !0
}), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) {
var e = "function" == typeof t && t.constructor;
return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name));
}, e.mark = function (t) {
return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t;
}, e.awrap = function (t) {
return {
__await: t
};
}, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () {
return this;
}), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) {
void 0 === i && (i = Promise);
var a = new AsyncIterator(wrap(t, r, n, o), i);
return e.isGeneratorFunction(r) ? a : a.next().then(function (t) {
return t.done ? t.value : a.next();
});
}, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () {
return this;
}), define(g, "toString", function () {
return "[object Generator]";
}), e.keys = function (t) {
var e = Object(t),
r = [];
for (var n in e) r.push(n);
return r.reverse(), function next() {
for (; r.length;) {
var t = r.pop();
if (t in e) return next.value = t, next.done = !1, next;
}
return next.done = !0, next;
};
}, e.values = values, Context.prototype = {
constructor: Context,
reset: function (e) {
if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t);
},
stop: function () {
this.done = !0;
var t = this.tryEntries[0].completion;
if ("throw" === t.type) throw t.arg;
return this.rval;
},
dispatchException: function (e) {
if (this.done) throw e;
var r = this;
function handle(n, o) {
return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o;
}
for (var o = this.tryEntries.length - 1; o >= 0; --o) {
var i = this.tryEntries[o],
a = i.completion;
if ("root" === i.tryLoc) return handle("end");
if (i.tryLoc <= this.prev) {
var c = n.call(i, "catchLoc"),
u = n.call(i, "finallyLoc");
if (c && u) {
if (this.prev < i.catchLoc) return handle(i.catchLoc, !0);
if (this.prev < i.finallyLoc) return handle(i.finallyLoc);
} else if (c) {
if (this.prev < i.catchLoc) return handle(i.catchLoc, !0);
} else {
if (!u) throw Error("try statement without catch or finally");
if (this.prev < i.finallyLoc) return handle(i.finallyLoc);
}
}
}
},
abrupt: function (t, e) {
for (var r = this.tryEntries.length - 1; r >= 0; --r) {
var o = this.tryEntries[r];
if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) {
var i = o;
break;
}
}
i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null);
var a = i ? i.completion : {};
return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a);
},
complete: function (t, e) {
if ("throw" === t.type) throw t.arg;
return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y;
},
finish: function (t) {
for (var e = this.tryEntries.length - 1; e >= 0; --e) {
var r = this.tryEntries[e];
if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y;
}
},
catch: function (t) {
for (var e = this.tryEntries.length - 1; e >= 0; --e) {
var r = this.tryEntries[e];
if (r.tryLoc === t) {
var n = r.completion;
if ("throw" === n.type) {
var o = n.arg;
resetTryEntry(r);
}
return o;
}
}
throw Error("illegal catch attempt");
},
delegateYield: function (e, r, n) {
return this.delegate = {
iterator: values(e),
resultName: r,
nextLoc: n
}, "next" === this.method && (this.arg = t), y;
}
}, e;
}
function _setPrototypeOf(t, e) {
return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
return t.__proto__ = e, t;
}, _setPrototypeOf(t, e);
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
/**
* Recovery strategy for form errors
*/
var RecoveryStrategy;
(function (RecoveryStrategy) {
/** Reset the form to its initial state */
RecoveryStrategy["RESET_FORM"] = "reset_form";
/** Retry the last operation that caused the error */
RecoveryStrategy["RETRY_OPERATION"] = "retry_operation";
/** Ignore the error and continue */
RecoveryStrategy["IGNORE"] = "ignore";
/** Custom recovery strategy defined by the user */
RecoveryStrategy["CUSTOM"] = "custom";
})(RecoveryStrategy || (RecoveryStrategy = {}));
/**
* Default fallback component for form errors
*/
var DefaultFallback = function DefaultFallback(_ref) {
var errorInfo = _ref.errorInfo,
resetErrorBoundary = _ref.resetErrorBoundary,
recover = _ref.recover;
return React__default.createElement("div", {
className: "reform-error-boundary"
}, React__default.createElement("h2", null, "Something went wrong in the form"), React__default.createElement("details", null, React__default.createElement("summary", null, "Error details"), React__default.createElement("p", null, errorInfo.error.message), React__default.createElement("pre", null, errorInfo.error.stack)), React__default.createElement("div", {
className: "reform-error-actions"
}, React__default.createElement("button", {
onClick: function onClick() {
return recover(RecoveryStrategy.RESET_FORM);
},
className: "reform-error-reset-btn"
}, "Reset Form"), React__default.createElement("button", {
onClick: resetErrorBoundary,
className: "reform-error-retry-btn"
}, "Try Again")));
};
/**
* Error boundary component for Reform forms
*
* Catches errors in form components and displays a fallback UI
*
* @template T - The type of form data
*
* @example
* // Basic usage
* <FormErrorBoundary>
* <MyFormComponent />
* </FormErrorBoundary>
*
* @example
* // With custom fallback component
* <FormErrorBoundary
* FallbackComponent={MyErrorFallback}
* onError={(error) => logError(error)}
* formGroups={reform.getGroups()}
* >
* <MyFormComponent />
* </FormErrorBoundary>
*/
var FormErrorBoundary = /*#__PURE__*/function (_React$Component) {
function FormErrorBoundary(props) {
var _this;
_this = _React$Component.call(this, props) || this;
_this.resetErrorBoundary = function () {
// Reset the error state
_this.setState({
hasError: false,
errorInfo: null
});
// Call onReset callback if provided
if (_this.props.onReset) {
_this.props.onReset();
}
};
_this.recover = function (strategy) {
if (typeof strategy === 'function') {
// Custom recovery function
strategy(_this.state.errorInfo);
_this.resetErrorBoundary();
return;
}
// Apply the specified recovery strategy
switch (strategy) {
case RecoveryStrategy.RESET_FORM:
// Reset form logic would be implemented by the consumer
break;
case RecoveryStrategy.RETRY_OPERATION:
// Just reset the error boundary to retry
break;
case RecoveryStrategy.IGNORE:
// Just reset the error boundary
break;
case RecoveryStrategy.CUSTOM:
// Call custom recovery handler if provided
if (_this.props.onCustomRecovery && _this.state.errorInfo) {
_this.props.onCustomRecovery(_this.state.errorInfo);
}
break;
}
_this.resetErrorBoundary();
};
_this.state = {
hasError: false,
errorInfo: null
};
return _this;
}
_inheritsLoose(FormErrorBoundary, _React$Component);
FormErrorBoundary.getDerivedStateFromError = function getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI
return {
hasError: true
};
};
var _proto = FormErrorBoundary.prototype;
_proto.componentDidCatch = function componentDidCatch(error, errorInfo) {
// Create error info object
var fullErrorInfo = {
error: error,
errorInfo: errorInfo,
formGroups: this.props.formGroups,
context: {}
};
// Update state with error info
this.setState({
errorInfo: fullErrorInfo
});
// Call onError callback if provided
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
};
_proto.render = function render() {
var _this$state = this.state,
hasError = _this$state.hasError,
errorInfo = _this$state.errorInfo;
var _this$props = this.props,
fallback = _this$props.fallback,
fallbackRender = _this$props.fallbackRender,
_this$props$FallbackC = _this$props.FallbackComponent,
FallbackComponent = _this$props$FallbackC === void 0 ? DefaultFallback : _this$props$FallbackC,
children = _this$props.children;
if (hasError && errorInfo) {
var fallbackProps = {
errorInfo: errorInfo,
resetErrorBoundary: this.resetErrorBoundary,
recover: this.recover
};
// Render the appropriate fallback UI
if (React__default.isValidElement(fallback)) {
return fallback;
}
if (typeof fallbackRender === 'function') {
return fallbackRender(fallbackProps);
}
return React__default.createElement(FallbackComponent, Object.assign({}, fallbackProps));
}
return children;
};
return FormErrorBoundary;
}(React__default.Component);
// Add static displayName property
FormErrorBoundary.displayName = 'FormErrorBoundary';
/**
* Hook to create a form error boundary with the current form groups
*
* @template T - The type of form data
* @param formGroups - Current form groups
* @param options - Additional options for the error boundary
* @returns A FormErrorBoundary component with the current form groups
*
* @example
* // In a form component
* const reform = useReform(config);
* const ErrorBoundary = useFormErrorBoundary(reform.getGroups());
*
* return (
* <ErrorBoundary>
* <MyFormFields />
* </ErrorBoundary>
* );
*/
var useFormErrorBoundary = function useFormErrorBoundary(formGroups, options) {
return React__default.useMemo(function () {
// Create the component with children
var ErrorBoundaryWrapper = function ErrorBoundaryWrapper(_ref2) {
var children = _ref2.children;
return React__default.createElement(FormErrorBoundary, Object.assign({
formGroups: formGroups
}, options), children);
};
// Add display name to the component
ErrorBoundaryWrapper.displayName = 'FormErrorBoundaryWrapper';
return ErrorBoundaryWrapper;
}, [formGroups, options]);
};
/**
* Component to display formatted error messages
*/
var ErrorMessage = function ErrorMessage(_ref) {
var error = _ref.error,
_ref$className = _ref.className,
className = _ref$className === void 0 ? "reform-error" : _ref$className,
_ref$showIcon = _ref.showIcon,
showIcon = _ref$showIcon === void 0 ? true : _ref$showIcon;
if (!error) return null;
// Handle string errors (backward compatibility)
if (typeof error === "string") {
return React__default.createElement("div", {
className: className
}, showIcon && React__default.createElement("span", {
className: "reform-error-icon"
}, "\u26A0\uFE0F"), React__default.createElement("span", null, error));
}
var content = error.content,
format = error.format,
meta = error.meta;
// Handle different formats
switch (format) {
case "jsx":
return React__default.createElement("div", {
className: className,
"data-error-meta": JSON.stringify(meta || {})
}, content);
case "html":
return React__default.createElement("div", {
className: className,
"data-error-meta": JSON.stringify(meta || {}),
dangerouslySetInnerHTML: {
__html: content
}
});
case "markdown":
return React__default.createElement("div", {
className: className,
"data-error-meta": JSON.stringify(meta || {})
}, React__default.createElement("span", null, content));
case "plain":
default:
return React__default.createElement("div", {
className: className,
"data-error-meta": JSON.stringify(meta || {})
}, showIcon && React__default.createElement("span", {
className: "reform-error-icon"
}, "\u26A0\uFE0F"), React__default.createElement("span", null, content));
}
};
/**
* Hook to create a memoized callback that maintains reference equality
* even when dependencies change, but only updates the function when
* the result would be different.
*
* @param callback - The callback function to memoize
* @param deps - Dependencies array for the callback
* @returns Memoized callback function
*/
function useMemoizedCallback(callback, deps) {
// Store the previous callback and deps
var callbackRef = React.useRef(callback);
var depsRef = React.useRef(deps);
// Check if deps have changed in a way that would affect the callback
var hasRelevantChanges = deps.some(function (dep, i) {
return !Object.is(dep, depsRef.current[i]);
});
// Update refs if there are relevant changes
if (hasRelevantChanges) {
callbackRef.current = callback;
depsRef.current = deps;
}
// Return a stable callback that uses the latest implementation
return React.useCallback(function () {
return callbackRef.current.apply(callbackRef, arguments);
}, []);
}
/**
* Default error format configuration
*/
var defaultFormatConfig = {
format: "plain",
includeLabels: true,
includeRuleNames: false
};
/**
* Hook for formatting form error messages
*
* @param reform - Reform hook return value
* @param initialConfig - Initial error format configuration
* @returns Form error formatter methods
*/
var useErrorFormatter = function useErrorFormatter(reform, initialConfig) {
// Memoize the initial config to prevent unnecessary re-renders
var memoizedInitialConfig = React.useMemo(function () {
return initialConfig;
}, [
// Stringify the config to compare by value instead of reference
JSON.stringify(initialConfig)]);
// Merge default config with initial config
var _useState = React.useState(function () {
return _extends({}, defaultFormatConfig, memoizedInitialConfig);
}),
formatConfig = _useState[0],
setFormatConfig = _useState[1];
// Store custom transformers
var transformersRef = React.useRef({});
/**
* Get the appropriate transformer for a field
*/
var getTransformer = React.useCallback(function (fieldPath) {
// Check for field-specific transformer
if (transformersRef.current[fieldPath]) {
return transformersRef.current[fieldPath];
}
// Check for global transformer
if (transformersRef.current["*"]) {
return transformersRef.current["*"];
}
return undefined;
}, [] // No dependencies as we're using ref
);
/**
* Default transformer for error messages
*/
var defaultTransformer = useMemoizedCallback(function (message, fieldPath, groupIndex) {
var groups = reform.getGroups();
var group = groups[groupIndex] || {
id: "",
data: {}
};
var labels = reform.config.labels || {};
// Handle nested labels with lodash get
var fieldLabel = typeof fieldPath === "string" ? get(labels, fieldPath) || get(labels, fieldPath) : undefined;
var content = message;
// Include field label if configured
if (formatConfig.includeLabels && fieldLabel) {
content = fieldLabel + ": " + content;
}
return {
content: content,
format: formatConfig.format,
fieldPath: fieldPath,
fieldLabel: fieldLabel,
meta: {
groupId: group.id,
groupIndex: groupIndex
}
};
}, [formatConfig, reform.getGroups, reform.config.labels]);
/**
* Format a field error message
*/
var formatFieldError = useMemoizedCallback(function (fieldPath, message, groupIndex) {
var transformer = getTransformer(fieldPath);
var groups = reform.getGroups();
var group = groups[groupIndex] || {
id: "",
data: {}
};
if (transformer) {
return transformer.transform(message, fieldPath, {
group: group,
groupIndex: groupIndex,
labels: reform.config.labels || {},
formatConfig: formatConfig
});
}
return defaultTransformer(message, fieldPath, groupIndex);
}, [defaultTransformer, formatConfig, getTransformer, reform.getGroups, reform.config.labels]);
/**
* Format a group-level error message
*/
var formatGroupError = useMemoizedCallback(function (message, groupIndex) {
var groups = reform.getGroups();
var group = groups[groupIndex] || {
id: "",
data: {}
};
return {
content: message,
format: formatConfig.format,
meta: {
groupId: group.id,
groupIndex: groupIndex,
isGroupError: true
}
};
}, [formatConfig.format, reform.getGroups]);
/**
* Format a form-level error message
*/
var formatFormError = React.useCallback(function (message) {
return {
content: message,
format: formatConfig.format,
meta: {
isFormError: true
}
};
}, [formatConfig.format]);
/**
* Register a custom error transformer
*/
var registerTransformer = React.useCallback(function (fieldPath, transformer) {
transformersRef.current[fieldPath] = transformer;
}, [] // No dependencies as we're using ref
);
/**
* Update the error format configuration
*/
var updateFormatConfig = React.useCallback(function (config) {
setFormatConfig(function (prev) {
return _extends({}, prev, config);
});
}, [] // No dependencies as we're using setState with function
);
// Return memoized object to prevent unnecessary re-renders
return React.useMemo(function () {
return {
formatFieldError: formatFieldError,
formatGroupError: formatGroupError,
formatFormError: formatFormError,
registerTransformer: registerTransformer,
updateFormatConfig: updateFormatConfig
};
}, [formatFieldError, formatGroupError, formatFormError, registerTransformer, updateFormatConfig]);
};
/**
* Hook wrapper for error formatting in Reform forms
*
* @param reform - Reform hook return value
* @param initialConfig - Initial error format configuration
* @returns Form error formatter methods
*
* @example
* // Basic usage
* const reform = useReform<UserForm>({...});
* const errorFormatter = useReformErrorFormatter(reform);
*
* // With custom configuration
* const errorFormatter = useReformErrorFormatter(reform, {
* format: 'jsx',
* includeLabels: true
* });
*
* // Format field error
* const error = errorFormatter.formatFieldError('email', 'Invalid email', 0);
*
* // Use in JSX
* <ErrorMessage error={error} />
*/
var useReformErrorFormatter = function useReformErrorFormatter(reform, initialConfig) {
return useErrorFormatter(reform, initialConfig);
};
/**
* Default error renderer that returns a simple error message
*/
var defaultErrorRenderer = function defaultErrorRenderer(_ref) {
var error = _ref.error;
var errorMessage = Array.isArray(error) ? error[0] : error;
return React__default.createElement("div", {
className: "reform-error"
}, errorMessage);
};
/**
* Hook for managing custom error renderers in Reform forms
*
* @template T - The type of form data
* @param groups - The form groups
* @returns Object with error renderer utilities
*
* @example
* // Basic usage
* const reform = useReform(config);
* const { registerErrorRenderer, renderError } = useErrorRenderer(reform.getGroups());
*
* // Register a custom renderer for email fields
* useEffect(() => {
* const unregister = registerErrorRenderer({
* field: 'email',
* renderer: ({ error }) => <EmailErrorMessage error={error} />
* });
*
* return unregister;
* }, []);
*
* // Use the renderer in your form
* return (
* <div>
* <input {...reform.register(0, 'email')} />
* {renderError('email', reform.getFieldError(0, 'email'), 0)}
* </div>
* );
*/
var useErrorRenderer = function useErrorRenderer(groups) {
// State for the error renderer context
var _useState = React.useState({
defaultRenderer: defaultErrorRenderer,
fieldRenderers: [],
showAllErrors: false,
contextData: {}
}),
context = _useState[0],
setContext = _useState[1];
/**
* Register a field-specific error renderer
*/
var registerErrorRenderer = useMemoizedCallback(function (config) {
var field = config.field,
renderer = config.renderer,
_config$priority = config.priority,
priority = _config$priority === void 0 ? 0 : _config$priority;
// Add the new renderer to the list
setContext(function (prev) {
var newRenderers = [].concat(prev.fieldRenderers, [{
field: field,
renderer: renderer,
priority: priority
}]);
// Sort by priority (higher numbers first)
newRenderers.sort(function (a, b) {
return (b.priority || 0) - (a.priority || 0);
});
return _extends({}, prev, {
fieldRenderers: newRenderers
});
});
// Return a function to unregister the renderer
return function () {
setContext(function (prev) {
return _extends({}, prev, {
fieldRenderers: prev.fieldRenderers.filter(function (r) {
return r !== config;
})
});
});
};
}, []);
/**
* Set the default error renderer
*/
var setDefaultRenderer = useMemoizedCallback(function (renderer) {
setContext(function (prev) {
return _extends({}, prev, {
defaultRenderer: renderer
});
});
}, []);
/**
* Check if a field matches a field pattern
*/
var fieldMatches = useMemoizedCallback(function (fieldPattern, field) {
if (typeof fieldPattern === 'function') {
return fieldPattern(field);
}
if (fieldPattern instanceof RegExp) {
return fieldPattern.test(String(field));
}
return fieldPattern === field;
}, []);
/**
* Get the appropriate renderer for a field
*/
var getRendererForField = useMemoizedCallback(function (field) {
// Find the first matching renderer
var matchingConfig = context.fieldRenderers.find(function (config) {
return fieldMatches(config.field, field);
});
return (matchingConfig == null ? void 0 : matchingConfig.renderer) || context.defaultRenderer;
}, [context.fieldRenderers, context.defaultRenderer, fieldMatches]);
/**
* Render an error for a specific field
*/
var renderError = useMemoizedCallback(function (field, error, groupIndex) {
if (!error) return null;
var renderer = getRendererForField(field);
var value = groupIndex >= 0 && groupIndex < groups.length ? groups[groupIndex].data[field] : undefined;
var props = {
field: field,
error: error,
value: value,
groupIndex: groupIndex,
groups: groups,
meta: context.contextData
};
return renderer(props);
}, [groups, context.contextData, getRendererForField]);
/**
* Set whether to show all errors or just the first one
*/
var setShowAllErrors = useMemoizedCallback(function (showAll) {
setContext(function (prev) {
return _extends({}, prev, {
showAllErrors: showAll
});
});
}, []);
/**
* Update the context data
*/
var updateContextData = useMemoizedCallback(function (data) {
setContext(function (prev) {
return _extends({}, prev, {
contextData: _extends({}, prev.contextData, data)
});
});
}, []);
// Return memoized object to prevent unnecessary re-renders
return React.useMemo(function () {
return {
registerErrorRenderer: registerErrorRenderer,
setDefaultRenderer: setDefaultRenderer,
renderError: renderError,
getRendererForField: getRendererForField,
setShowAllErrors: setShowAllErrors,
updateContextData: updateContextData,
context: context
};
}, [registerErrorRenderer, setDefaultRenderer, renderError, getRendererForField, setShowAllErrors, updateContextData, context]);
};
/**
* Hook for managing custom error renderers in Reform forms
*
* @template T - The type of form data
* @param reform - The Reform hook return value
* @returns Object with error renderer utilities
*
* @example
* // Basic usage
* const reform = useReform(config);
* const errorRenderer = useReformErrorRenderer(reform);
*
* // Register a custom renderer for email fields
* useEffect(() => {
* const unregister = errorRenderer.registerErrorRenderer({
* field: 'email',
* renderer: ({ error }) => <EmailErrorMessage error={error} />
* });
*
* return unregister;
* }, []);
*
* // Use the renderer in your form
* return (
* <div>
* <input {...reform.register(0, 'email')} />
* {errorRenderer.renderFieldError(0, 'email')}
* </div>
* );
*/
var useReformErrorRenderer = function useReformErrorRenderer(reform) {
var groups = reform.getGroups();
var errorRenderer = useErrorRenderer(groups);
// Enhanced API that integrates with Reform
return React.useMemo(function () {
return _extends({}, errorRenderer, {
/**
* Render an error for a specific field using Reform's error state
*
* @param groupIndex - The group index
* @param field - The field path
* @returns The rendered error
*/
renderFieldError: function renderFieldError(groupIndex, field) {
var error = reform.getFieldError(groupIndex, field);
return errorRenderer.renderError(field, error, groupIndex);
},
/**
* Render all errors for a group
*
* @param groupIndex - The group index
* @returns Array of rendered errors
*/
renderGroupErrors: function renderGroupErrors(groupIndex) {
var errors = reform.getGroupErrors(groupIndex);
return Object.entries(errors).map(function (_ref) {
var field = _ref[0],
error = _ref[1];
return errorRenderer.renderError(field, error, groupIndex);
});
}
});
}, [errorRenderer, reform]);
};
var useReformErrors = function useReformErrors(reform) {
var _reform$formMethods = reform.formMethods,
formState = _reform$formMethods.formState,
clearFormErrors = _reform$formMethods.clearErrors;
React.useEffect(function () {
// You can add logging here for debugging
if (Object.keys(formState.errors || {}).length > 0) {
console.log('Current form errors:', formState.errors);
}
}, [formState.errors]);
// Helper function to extract error message from various error formats
var extractErrorMessage = function extractErrorMessage(error) {
if (!error) return undefined;
// Handle array of errors (like in dateRange)
if (Array.isArray(error)) {
var _error$;
// Return the first error message in the array
return (_error$ = error[0]) == null ? void 0 : _error$.message;
}
// Handle standard error object with message
return error.message;
};
// Type-safe way to access errors with string keys
var getErrorByKey = function getErrorByKey(key) {
// Use type assertion to access errors with string keys
return formState.errors[key];
};
var getError = function getError(groupIndex, field) {
var _formState$errors;
// First check if the error is in the flattened format (e.g., "0.users")
var flatKey = groupIndex + "." + field;
var flatError = getErrorByKey(flatKey);
if (flatError) {
return extractErrorMessage(flatError);
}
// If not found in flat format, check in the nested format
var fieldError = (_formState$errors = formState.errors) == null || (_formState$errors = _formState$errors.groups) == null || (_formState$errors = _formState$errors[groupIndex]) == null ? void 0 : _formState$errors.data;
if (!fieldError) return undefined;
return extractErrorMessage(fieldError[field]);
};
var getFieldError = function getFieldError(groupIndex, fieldName) {
return getError(groupIndex, fieldName);
};
var getGroupErrors = function getGroupErrors(groupIndex) {
var result = {};
// Check for flattened errors first
Object.keys(formState.errors || {}).forEach(function (key) {
// Check if the key matches the pattern "groupIndex.fieldName"
var match = key.match("^" + groupIndex + "\\.(.+)$");
if (match) {
var fieldName = match[1];
var errorMessage = extractErrorMessage(getErrorByKey(key));
if (errorMessage) {
result[fieldName] = errorMessage;
}
}
});
// If no flattened errors found, check nested format
if (Object.keys(result).length === 0) {
var _formState$errors2;
var groupData = (_formState$errors2 = formState.errors) == null || (_formState$errors2 = _formState$errors2.groups) == null || (_formState$errors2 = _formState$errors2[groupIndex]) == null ? void 0 : _formState$errors2.data;
if (groupData) {
Object.keys(groupData).forEach(function (field) {
var errorMessage = extractErrorMessage(groupData[field]);
if (errorMessage) {
result[field] = errorMessage;
}
});
}
}
return result;
};
var getFieldErrors = function getFieldErrors(index) {
var result = {};
// Check for flattened errors first
Object.keys(formState.errors || {}).forEach(function (key) {
var match = key.match("^" + index + "\\.(.+)$");
if (match) {
var fieldName = match[1];
result[fieldName] = extractErrorMessage(getErrorByKey(key));
}
});
// If no flattened errors found, check nested format
if (Object.keys(result).length === 0) {
var _formState$errors3;
var groupData = (_formState$errors3 = formState.errors) == null || (_formState$errors3 = _formState$errors3.groups) == null || (_formState$errors3 = _formState$errors3[index]) == null ? void 0 : _formState$errors3.data;
if (groupData) {
Object.keys(groupData).forEach(function (field) {
result[field] = extractErrorMessage(groupData[field]);
});
}
}
return result;
};
// Error Clearers
var clearErrors = function clearErrors() {
clearFormErrors();
};
var clearFieldError = function clearFieldError(groupIndex, fieldName) {
// The issue is here - clearFormErrors might only accept a string or undefined
// Let's fix by ensuring we're passing the correct type
var fieldPath = "groups." + groupIndex + ".data." + fieldName;
clearFormErrors(fieldPath);
};
var clearGroupError = function clearGroupError(groupIndex) {
var groupPath = "groups." + groupIndex;
clearFormErrors(groupPath);
};
var clearGlobalError = function clearGlobalError() {
clearFormErrors('root');
};
// // Error Setters
// const setFieldError = (
// groupIndex: number,
// fieldName: string,
// message: string
// ) => {
// const fieldPath = `groups.${groupIndex}.data.${fieldName}` as FormDataPath<
// T
// >;
// setError(fieldPath, {
// type: 'manual',
// message,
// });
// };
// const setGroupError = (groupIndex: number, message: string) => {
// const groupPath = `groups.${groupIndex}` as FormDataPath<T>;
// setError(groupPath, {
// type: 'manual',
// message,
// });
// };
// const setGlobalError = (message: string) => {
// setError('root' as FormDataPath<T>, {
// type: 'manual',
// message,
// });
// };
var hasGroupErrors = function hasGroupErrors(groupIndex) {
var _formState$errors4, _formState$errors5;
// Check for flattened errors first
for (var key in formState.errors || {}) {
if (key.startsWith(groupIndex + ".")) {
return true;
}
}
// If no flattened errors, check nested format
return !!((_formState$errors4 = formState.errors) != null && (_formState$errors4 = _formState$errors4.groups) != null && (_formState$errors4 = _formState$errors4[groupIndex]) != null && _formState$errors4.data) && Object.keys(((_formState$errors5 = formState.errors) == null || (_formState$errors5 = _formState$errors5.groups) == null || (_formState$errors5 = _formState$errors5[groupIndex]) == null ? void 0 : _formState$errors5.data) || {}).length > 0;
};
var getAllErrors = function getAllErrors() {
var _formState$errors6;
var allErrors = {};
var errorsRecord = formState.errors;
// Include flattened errors
Object.keys(formState.errors || {}).forEach(function (key) {
// Only include errors that match the pattern "groupIndex.fieldName"
if (/^\d+\.\w+$/.test(key)) {
allErrors[key] = errorsRecord[key];
}
});
// Also check nested format
if ((_formState$errors6 = formState.errors) != null && _formState$errors6.groups) {
Object.keys(formState.errors.groups).forEach(function (groupIndexStr) {
var _formState$errors7;
var groupIndex = Number(groupIndexStr);
var groupData = (_formState$errors7 = formState.errors) == null || (_formState$errors7 = _formState$errors7.groups) == null || (_formState$errors7 = _formState$errors7[groupIndex]) == null ? void 0 : _formState$errors7.data;
if (groupData) {
Object.keys(groupData).forEach(function (field) {
if (groupData[field]) {
var key = groupIndex + "." + field;
// Only add if not already added from flattened format
if (!allErrors[key]) {
allErrors[key] = groupData[field];
}
}
});
}
});
}
return allErrors;
};
var isGroupValid = function isGroupValid(groupIndex, requiredFields) {
if (hasGroupErrors(groupIndex)) {
return false;
}
var group = reform.getGroups()[groupIndex];
if (!group) return false;
return requiredFields.every(function (field) {
var value = group.data[field];
if (Array.isArray(value)) {
return value.length > 0;
}
if (value === null || value === undefined) {
return false;
}
if (typeof value === 'string') {
return value.trim() !== '';
}
return true;
});
};
var areAllGroupsValid = function areAllGroupsValid(requiredFields) {
var groups = reform.getGroups();
return groups.every(function (_, index) {
return isGroupValid(index, requiredFields);
});
};
var isFormSubmittable = function isFormSubmittable(requiredFields) {
return Object.keys(formState.errors || {}).length === 0 && areAllGroupsValid(requiredFields);
};
var canAddNewGroup = function canAddNewGroup(requiredFields) {
return areAllGroupsValid(requiredFields);
};
return {
errors: getAllErrors(),
hasErrors: Object.keys(formState.errors || {}).length > 0,
getFieldError: getFieldError,
getGroupErrors: getGroupErrors,
getFieldErrors: getFieldErrors,
clearErrors: clearErrors,
clearFieldError: clearFieldError,
clearGroupError: clearGroupError,
clearGlobalError: clearGlobalError,
// setFieldError,
// setGroupError,
// setGlobalError,
getError: getError,
hasGroupErrors: hasGroupErrors,
isGroupValid: isGroupValid,
areAllGroupsValid: areAllGroupsValid,
isFormSubmittable: isFormSubmittable,
canAddNewGroup: canAddNewGroup
};
};
var useFormController = function useFormController(methods) {
return {
control: methods.control
};
};
/**
* Hook for advanced form reset functionality
*
* @param props - Hook properties
* @returns Object with reset methods
*/
var useFormReset = function useFormReset(_ref) {
var methods = _ref.methods,
defaultData = _ref.defaultData,
minGroups = _ref.minGroups;
/**
* Resets the form to its initial state or to provided default values
*/
var resetForm = function resetForm(defaultValues, options) {
methods.reset(defaultValues, options);
};
/**
* Resets the form to a clean state with the specified number of groups
*
* @param groupCount - Number of groups to initialize (defaults to minGroups)
*/
var resetToCleanState = function resetToCleanState(groupCount) {
if (groupCount === void 0) {
groupCount = minGroups;
}
var cleanGroups = Array.from({
length: groupCount
}).map(function () {
return {
id: Math.random().toString(36).substr(2, 9),
data: _extends({}, defaultData)
};
});
methods.reset({
groups: cleanGroups
});
};
/**
* Resets a specific group to its default state
*
* @param index - Index of the group to reset
*/
var resetGroup = function resetGroup(index) {
var currentGroups = methods.getValues().groups;
if (index >= 0 && index < currentGroups.length) {
var updatedGroups = [].concat(currentGroups);
updatedGroups[index] = {
id: updatedGroups[index].id,
data: _extends({}, defaultData)
};
methods.reset({
groups: updatedGroups
});
}
};
return {
resetForm: resetForm,
resetToCleanState: resetToCleanState,
resetGroup: resetGroup
};
};
var useFormState = function useFormState(methods) {
return {
fieldState: {
isDirty: function isDirty(index, fieldName) {
return methods.getFieldState("groups." + index + ".data." + String(fieldName)).isDirty;
},
isTouched: function isTouched(index, fieldName) {
return methods.getFieldState("groups." + index + ".data." + String(fieldName)).isTouched;
},
get: function get(index, fieldName) {
return methods.getFieldState("groups." + index + ".data." + String(fieldName));
}
},
formState: {
isDirty: methods.formState.isDirty,
isValid: methods.formState.isValid,
touched: Object.keys(methods.formState.touchedFields).length > 0,
isSubmitting: methods.formState.isSubmitting,
submitCount: methods.formState.submitCount,
isValidating: methods.formState.isValidating
},
reset: methods.reset,
trigger: methods.trigger,
watch: methods.watch
};
};
// Memoize this function to prevent recreation on each render
var createPathString = function createPathString(index, field) {
return "groups." + index + ".data." + field;
};
var useReformGroups = function useReformGroups(_ref) {
var methods = _ref.methods,
minGroups = _ref.minGroups,
maxGroups = _ref.maxGroups,
defaultData = _ref.defaultData,
onChange = _ref.onChange;
// Memoize defaultData to prevent unnecessary re-renders
var memoizedDefaultData = React.useMemo(function () {
return defaultData;
}, [
// Stringify defaultData to compare by value instead of reference
JSON.stringify(defaultData)]);
var _useFieldArray = reactHookForm.useFieldArray({
control: methods.control,
name: 'groups'
}),
fields = _useFieldArray.fields,
append = _useFieldArray.append,
remove = _useFieldArray.remove,
move = _useFieldArray.move,
update = _useFieldArray.update,
replace = _useFieldArray.replace;
// Memoize this function to prevent recreation on each render
var createNewGroup = React.useCallback(function (data) {
if (data === void 0) {
data = memoizedDefaultData;
}
return {
id: Math.random().toString(36).substring(2, 9),
data: _extends({}, data)
};
}, [memoizedDefaultData]);
var addGroup = useMemoizedCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
var isValid;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
if (!(fields.length >= maxGroups)) {
_context.next = 2;
break;
}
return _context.abrupt("return", {
isValid: false,
message: "Maximum " + maxGroups + " groups allowed"
});
case 2:
append(createNewGroup());
_context.next = 5;
re