react-hook-formify
Version:
A smart wrapper around react-hook-form + zustand
242 lines (232 loc) • 9.99 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// index.ts
var index_exports = {};
__export(index_exports, {
Field: () => field_default,
Form: () => form_default,
useFormStore: () => useFormStore
});
module.exports = __toCommonJS(index_exports);
// src/components/form.tsx
var import_react2 = require("react");
var import_react_hook_form = require("react-hook-form");
var import_react_i18next = require("react-i18next");
var import_yup = require("@hookform/resolvers/yup");
// ../../../node_modules/zustand/esm/vanilla.mjs
var createStoreImpl = (createState) => {
let state;
const listeners = /* @__PURE__ */ new Set();
const setState = (partial, replace) => {
const nextState = typeof partial === "function" ? partial(state) : partial;
if (!Object.is(nextState, state)) {
const previousState = state;
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
listeners.forEach((listener) => listener(state, previousState));
}
};
const getState = () => state;
const getInitialState = () => initialState;
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const api = { setState, getState, getInitialState, subscribe };
const initialState = state = createState(setState, getState, api);
return api;
};
var createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
// ../../../node_modules/zustand/esm/react.mjs
var import_react = __toESM(require("react"), 1);
var identity = (arg) => arg;
function useStore(api, selector = identity) {
const slice = import_react.default.useSyncExternalStore(
api.subscribe,
import_react.default.useCallback(() => selector(api.getState()), [api, selector]),
import_react.default.useCallback(() => selector(api.getInitialState()), [api, selector])
);
import_react.default.useDebugValue(slice);
return slice;
}
var createImpl = (createState) => {
const api = createStore(createState);
const useBoundStore = (selector) => useStore(api, selector);
Object.assign(useBoundStore, api);
return useBoundStore;
};
var create = ((createState) => createState ? createImpl(createState) : createImpl);
// src/store/form.store.ts
var useFormStore = create((set) => ({
formData: {},
setFormData: (key, data) => set((state) => ({
formData: { ...state.formData, [key]: data }
})),
resetForm: () => set({ formData: {} })
}));
// src/utils/debounce.ts
function debounce(func, wait) {
let timeout;
const debounced = (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
debounced.cancel = () => clearTimeout(timeout);
return debounced;
}
// src/utils/validation.ts
var yup = __toESM(require("yup"));
var generateSchema = (fields = [], t) => {
const shape = {};
const getBaseValidator = (field) => {
if (typeof field?.type === "function") return field?.type(yup);
switch (field?.type) {
case "email":
return yup.string().email(t("validation.email"));
case "number":
return yup.number().transform((v, o) => o === "" ? null : v).nullable().typeError(t("validation.number"));
case "object":
return yup.object().nullable().transform((value, originalValue) => {
return originalValue === "" ? null : value;
});
case "array":
return yup.array().nullable().transform((value, originalValue) => {
return originalValue === "" ? null : value;
});
case "file":
return yup.mixed().nullable().transform((value, originalValue) => {
return originalValue === "" ? null : value;
}).test("fileType", t("validation.invalid"), (value) => {
if (value == null) return true;
if (value instanceof File) return true;
if (Array.isArray(value) && value.length > 0) return true;
return typeof value === "object" && Object.keys(value).length > 0;
});
case "boolean":
return yup.boolean().typeError(t("validation.boolean"));
case "date":
return yup.date().nullable().transform((v, o) => o === "" ? null : v).typeError(t("validation.date"));
case "string":
default:
return yup.string();
}
};
const applyRules = (schema, rules) => {
if (rules.required) schema = schema.required(t("validation.required"));
if (rules.min !== void 0) schema = schema.min?.(rules.min, t("validation.min", { min: rules.min })) ?? schema;
if (rules.max !== void 0) schema = schema.max?.(rules.max, t("validation.max", { max: rules.max })) ?? schema;
if (rules.matches) schema = schema.matches?.(rules.matches.regex, t(rules.matches.message || "validation.invalid")) ?? schema;
return schema;
};
fields.forEach((field) => {
if (field?.when && (field.when.field || Array.isArray(field.when.fields))) {
const whenFields = Array.isArray(field.when.fields) ? field.when.fields : [field.when.field];
const conditionFn = field.when.is;
const thenRules = field.when.then || {};
const otherwiseRules = field.when.otherwise || {};
shape[field.name] = yup.mixed().when(whenFields, (...args) => {
const values = args.slice(0, whenFields.length);
const base = getBaseValidator(field);
try {
const condition = typeof conditionFn === "function" ? conditionFn(...values) : conditionFn === values[0];
return condition ? applyRules(base, thenRules) : applyRules(base, otherwiseRules);
} catch (err) {
console.warn(`generateSchema: conditionFn error in field "${field.name}"`, err);
return base;
}
});
} else {
shape[field.name] = applyRules(getBaseValidator(field), field);
}
});
return yup.object().shape(shape);
};
// src/components/form.tsx
var import_jsx_runtime = require("react/jsx-runtime");
var FormComponent = (0, import_react2.forwardRef)(({ name = "form", children, onSubmit, enableStore, isLoading = false, fields = [] }, ref) => {
const { t } = (0, import_react_i18next.useTranslation)();
const initialValues = Object.fromEntries(fields.map((f) => [f.name, f.value ?? ""]));
const methods = (0, import_react_hook_form.useForm)({
defaultValues: initialValues,
values: initialValues,
resolver: (0, import_yup.yupResolver)(generateSchema(fields, t)),
mode: "all",
shouldFocusError: false,
reValidateMode: "onChange"
});
(0, import_react2.useEffect)(() => {
if (!enableStore) return;
const debouncedUpdate = debounce((values) => {
useFormStore.getState().setFormData(name, values);
}, 300);
const subscription = methods.watch((values) => {
debouncedUpdate(values);
});
return () => {
subscription.unsubscribe();
debouncedUpdate.cancel();
};
}, [enableStore, methods, methods.watch, name]);
const handleSubmit = async (values) => {
const isValid = await methods.trigger();
if (isValid) {
const renderedValues = Object.fromEntries(
fields.map((field) => {
const rawValue = values[field.name];
const transformed = typeof field.onSubmitValue === "function" ? field.onSubmitValue(rawValue) : rawValue;
return [field.name, transformed];
})
);
onSubmit?.({ values: renderedValues, ...methods });
} else {
console.log("Form error");
}
};
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_hook_form.FormProvider, { ...methods, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", { ref, onSubmit: methods.handleSubmit(handleSubmit), children: typeof children === "function" ? children({
...methods,
isLoading,
values: methods.watch(),
errors: methods.formState.errors
}) : children }) });
});
var form_default = FormComponent;
// src/components/field.tsx
var import_react_hook_form2 = require("react-hook-form");
var import_jsx_runtime2 = require("react/jsx-runtime");
var FieldComponent = ({ name, component, ...rest }) => {
const { control, getFieldState } = (0, import_react_hook_form2.useFormContext)();
const Component = component || null;
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_hook_form2.Controller, { name, control, render: ({ field }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Component, { ...getFieldState(name), ...rest, ...field }) });
};
var field_default = FieldComponent;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Field,
Form,
useFormStore
});