UNPKG

react-hook-formify

Version:

A smart wrapper around react-hook-form + zustand

182 lines (174 loc) 6.36 kB
// src/components/form.tsx import { forwardRef, useEffect } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { yupResolver } from "@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 import React from "react"; var identity = (arg) => arg; function useStore(api, selector = identity) { const slice = React.useSyncExternalStore( api.subscribe, () => selector(api.getState()), () => selector(api.getInitialState()) ); React.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, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, delay); }; } // src/utils/validation.ts import * as yup from "yup"; var generateSchema = (fields = [], t) => { const shape = {}; fields?.forEach((field) => { const getBaseValidator = () => { if (typeof field?.type === "function") { return field.type(yup); } switch (field.type) { case "email": return yup.string().email(t("validation.email")); case "object": return yup.object().typeError(t("validation.required")); case "array": return yup.array(); default: return yup[field.type || "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", { max: rules.min })); } if (rules.max !== void 0) { schema = schema.max(rules.max, t("validation.max", { max: rules.max })); } return schema; }; let baseValidator = getBaseValidator(); if (field?.when) { const whenFields = Array.isArray(field.when.fields) ? field.when.fields : [field.when.field]; const conditionFn = field.when.is; shape[field.name] = yup.mixed().when(whenFields, (...args) => { const values = args.slice(0, whenFields.length); const schema = getBaseValidator(); return conditionFn(...values) ? applyRules(schema, field.when.then || {}) : applyRules(schema, field.when.otherwise || {}); }); } else { shape[field.name] = applyRules(baseValidator, field); } }); return yup.object().shape(shape); }; // src/components/form.tsx import { jsx } from "react/jsx-runtime"; var FormComponent = forwardRef(({ name = "form", children, onSubmit, enableStore, isLoading = false, fields = [] }, ref) => { const { t } = useTranslation(); const initialValues = Object.fromEntries(fields.map((f) => [f.name, f.value ?? ""])); const methods = useForm({ defaultValues: initialValues, values: initialValues, resolver: yupResolver(generateSchema(fields, t)), mode: "onChange", shouldFocusError: false }); 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__ */ jsx(FormProvider, { ...methods, children: /* @__PURE__ */ 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 import { Controller, useFormContext } from "react-hook-form"; import { jsx as jsx2 } from "react/jsx-runtime"; var FieldComponent = ({ name, component, ...rest }) => { const { control, getFieldState } = useFormContext(); const Component = component || null; return /* @__PURE__ */ jsx2(Controller, { name, control, render: ({ field }) => /* @__PURE__ */ jsx2(Component, { ...getFieldState(name), ...rest, ...field }) }); }; var field_default = FieldComponent; export { field_default as Field, form_default as Form, useFormStore };