UNPKG

@next-safe-action/adapter-react-hook-form

Version:

This adapter offers a way to seamlessly integrate next-safe-action with react-hook-form.

1 lines 9.54 kB
{"version":3,"sources":["../src/hooks.ts","../src/index.ts"],"sourcesContent":["\"use client\";\n\nimport type { ValidationErrors } from \"next-safe-action\";\nimport type { Infer, InferIn, Schema } from \"next-safe-action/adapters/types\";\nimport type { HookSafeActionFn } from \"next-safe-action/hooks\";\nimport { useAction, useOptimisticAction } from \"next-safe-action/hooks\";\nimport * as React from \"react\";\nimport type { Resolver } from \"react-hook-form\";\nimport { useForm } from \"react-hook-form\";\nimport type {} from \"zod\";\nimport type { HookProps, UseHookFormActionHookReturn, UseHookFormOptimisticActionHookReturn } from \"./hooks.types\";\nimport type { ErrorMapperProps } from \"./index\";\nimport { mapToHookFormErrors } from \"./index\";\n\n/**\n * For more advanced use cases where you want full customization of the hooks used, you can\n * use this hook to map a validation errors object to a `FieldErrors` compatible with react-hook-form.\n * You can then pass the returned `hookFormValidationErrors` property to `useForm`'s `errors` prop.\n *\n * @param validationErrors Validation errors object from `next-safe-action`\n * @returns Object of `FieldErrors` compatible with react-hook-form\n */\nexport function useHookFormActionErrorMapper<S extends Schema | undefined>(\n\tvalidationErrors: ValidationErrors<S> | undefined,\n\tprops?: ErrorMapperProps\n) {\n\tconst propsRef = React.useRef(props);\n\n\tconst hookFormValidationErrors = React.useMemo(\n\t\t() => mapToHookFormErrors<S>(validationErrors, propsRef.current),\n\t\t[validationErrors]\n\t);\n\n\treturn {\n\t\thookFormValidationErrors,\n\t};\n}\n\n/**\n * This hook is a wrapper around `useAction` and `useForm` that makes it easier to use safe actions\n * with react-hook-form. It also maps validation errors to `FieldErrors` compatible with react-hook-form.\n *\n * @param safeAction The safe action\n * @param hookFormResolver A react-hook-form validation resolver\n * @param props Optional props for both `useAction`, `useForm` hooks and error mapper\n * @returns An object containing `action` and `form` controllers, `handleActionSubmit`, and `resetFormAndAction`\n */\nexport function useHookFormAction<\n\tServerError,\n\tS extends Schema | undefined,\n\tBAS extends readonly Schema[],\n\tCVE,\n\tCBAVE,\n\tData,\n\tFormContext = any,\n>(\n\tsafeAction: HookSafeActionFn<ServerError, S, BAS, CVE, CBAVE, Data>,\n\thookFormResolver: Resolver<S extends Schema ? Infer<S> : any, FormContext>,\n\tprops?: HookProps<ServerError, S, BAS, CVE, CBAVE, Data, FormContext>\n): UseHookFormActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data, FormContext> {\n\tconst action = useAction(safeAction, props?.actionProps);\n\n\tconst { hookFormValidationErrors } = useHookFormActionErrorMapper<S>(\n\t\taction.result.validationErrors as ValidationErrors<S> | undefined,\n\t\tprops?.errorMapProps\n\t);\n\n\tconst form = useForm<S extends Schema ? Infer<S> : any, FormContext>({\n\t\t...props?.formProps,\n\t\tresolver: hookFormResolver,\n\t\terrors: hookFormValidationErrors,\n\t});\n\n\tconst handleSubmitWithAction = form.handleSubmit(action.executeAsync);\n\n\tconst resetFormAndAction = () => {\n\t\tform.reset();\n\t\taction.reset();\n\t};\n\n\treturn {\n\t\taction,\n\t\tform,\n\t\thandleSubmitWithAction,\n\t\tresetFormAndAction,\n\t};\n}\n\n/**\n * This hook is a wrapper around `useOptimisticAction` and `useForm` that makes it easier to use safe actions\n * with react-hook-form. It also maps validation errors to `FieldErrors` compatible with react-hook-form.\n *\n * @param safeAction The safe action\n * @param hookFormResolver A react-hook-form validation resolver\n * @param props Required `currentState` and `updateFn` props for the action, and additional optional\n * props for both `useAction`, `useForm` hooks and error mapper\n * @returns An object containing `action` and `form` controllers, `handleActionSubmit`, and `resetFormAndAction`\n */\nexport function useHookFormOptimisticAction<\n\tServerError,\n\tS extends Schema | undefined,\n\tBAS extends readonly Schema[],\n\tCVE,\n\tCBAVE,\n\tData,\n\tState,\n\tFormContext = any,\n>(\n\tsafeAction: HookSafeActionFn<ServerError, S, BAS, CVE, CBAVE, Data>,\n\thookFormResolver: Resolver<S extends Schema ? Infer<S> : any, FormContext>,\n\tprops: HookProps<ServerError, S, BAS, CVE, CBAVE, Data, FormContext> & {\n\t\tactionProps: {\n\t\t\tcurrentState: State;\n\t\t\tupdateFn: (state: State, input: S extends Schema ? InferIn<S> : undefined) => State;\n\t\t};\n\t}\n): UseHookFormOptimisticActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data, State, FormContext> {\n\tconst action = useOptimisticAction(safeAction, props.actionProps);\n\n\tconst { hookFormValidationErrors } = useHookFormActionErrorMapper<S>(\n\t\taction.result.validationErrors as ValidationErrors<S> | undefined,\n\t\tprops.errorMapProps\n\t);\n\n\tconst form = useForm<S extends Schema ? Infer<S> : any, FormContext>({\n\t\t...props?.formProps,\n\t\tresolver: hookFormResolver,\n\t\terrors: hookFormValidationErrors,\n\t});\n\n\tconst handleSubmitWithAction = form.handleSubmit(action.executeAsync);\n\n\tconst resetFormAndAction = () => {\n\t\tform.reset();\n\t\taction.reset();\n\t};\n\n\treturn {\n\t\taction,\n\t\tform,\n\t\thandleSubmitWithAction,\n\t\tresetFormAndAction,\n\t};\n}\n\nexport type * from \"./hooks.types\";\n","/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */\n\nimport type { ValidationErrors } from \"next-safe-action\";\nimport type { Infer, Schema } from \"next-safe-action/adapters/types\";\nimport type { FieldError, FieldErrors } from \"react-hook-form\";\nimport type {} from \"zod\";\nimport type { ErrorMapperProps } from \"./index.types\";\n\n/**\n * Maps a validation errors object to an object of `FieldErrors` compatible with react-hook-form.\n * You should only call this function directly for advanced use cases, and prefer exported hooks.\n */\nexport function mapToHookFormErrors<S extends Schema | undefined>(\n\tvalidationErrors: ValidationErrors<S> | undefined,\n\tprops?: ErrorMapperProps\n) {\n\tif (!validationErrors || Object.keys(validationErrors).length === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst fieldErrors: FieldErrors<S extends Schema ? Infer<S> : any> = {};\n\n\tfunction mapper(ve: Record<string, any>, paths: string[] = []) {\n\t\t// Map through validation errors.\n\t\tfor (const key of Object.keys(ve)) {\n\t\t\t// If validation error is an object, recursively call mapper so we go one level deeper\n\t\t\t// at a time. Pass the current paths to the mapper as well.\n\t\t\tif (typeof ve[key] === \"object\" && ve[key] && !Array.isArray(ve[key])) {\n\t\t\t\tmapper(ve[key], [...paths, key]);\n\t\t\t}\n\n\t\t\t// We're just interested in the `_errors` field, which must be an array.\n\t\t\tif (key === \"_errors\" && Array.isArray(ve[key])) {\n\t\t\t\t// Initially set moving reference to root `fieldErrors` object.\n\t\t\t\tlet ref = fieldErrors as Record<string, any>;\n\n\t\t\t\t// Iterate through the paths, create nested objects if needed and move the reference.\n\t\t\t\tfor (let i = 0; i < paths.length - 1; i++) {\n\t\t\t\t\tconst p = paths[i]!;\n\t\t\t\t\tref[p] ??= {};\n\t\t\t\t\tref = ref[p];\n\t\t\t\t}\n\n\t\t\t\t// The actual path is the last one. If it's undefined, it means that we're at the root level.\n\t\t\t\tconst path = paths.at(-1) ?? \"root\";\n\n\t\t\t\t// Set the error for the current path.\n\t\t\t\tref[path] = {\n\t\t\t\t\ttype: \"validate\",\n\t\t\t\t\tmessage: ve[key].join(props?.joinBy ?? \" \"),\n\t\t\t\t} as FieldError;\n\t\t\t}\n\t\t}\n\t}\n\n\tmapper(validationErrors ?? {});\n\treturn fieldErrors;\n}\n\nexport type * from \"./index.types\";\n"],"mappings":";;;AAKA,SAAS,WAAW,2BAA2B;AAC/C,YAAY,WAAW;AAEvB,SAAS,eAAe;;;ACIjB,SAAS,oBACf,kBACA,OACC;AACD,MAAI,CAAC,oBAAoB,OAAO,KAAK,gBAAgB,EAAE,WAAW,GAAG;AACpE,WAAO;AAAA,EACR;AAEA,QAAM,cAA8D,CAAC;AAErE,WAAS,OAAO,IAAyB,QAAkB,CAAC,GAAG;AAE9D,eAAW,OAAO,OAAO,KAAK,EAAE,GAAG;AAGlC,UAAI,OAAO,GAAG,GAAG,MAAM,YAAY,GAAG,GAAG,KAAK,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG;AACtE,eAAO,GAAG,GAAG,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC;AAAA,MAChC;AAGA,UAAI,QAAQ,aAAa,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG;AAEhD,YAAI,MAAM;AAGV,iBAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AAC1C,gBAAM,IAAI,MAAM,CAAC;AACjB,cAAI,CAAC,MAAM,CAAC;AACZ,gBAAM,IAAI,CAAC;AAAA,QACZ;AAGA,cAAM,OAAO,MAAM,GAAG,EAAE,KAAK;AAG7B,YAAI,IAAI,IAAI;AAAA,UACX,MAAM;AAAA,UACN,SAAS,GAAG,GAAG,EAAE,KAAK,OAAO,UAAU,GAAG;AAAA,QAC3C;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,SAAO,oBAAoB,CAAC,CAAC;AAC7B,SAAO;AACR;;;ADnCO,SAAS,6BACf,kBACA,OACC;AACD,QAAM,WAAiB,aAAO,KAAK;AAEnC,QAAM,2BAAiC;AAAA,IACtC,MAAM,oBAAuB,kBAAkB,SAAS,OAAO;AAAA,IAC/D,CAAC,gBAAgB;AAAA,EAClB;AAEA,SAAO;AAAA,IACN;AAAA,EACD;AACD;AAWO,SAAS,kBASf,YACA,kBACA,OACkF;AAClF,QAAM,SAAS,UAAU,YAAY,OAAO,WAAW;AAEvD,QAAM,EAAE,yBAAyB,IAAI;AAAA,IACpC,OAAO,OAAO;AAAA,IACd,OAAO;AAAA,EACR;AAEA,QAAM,OAAO,QAAwD;AAAA,IACpE,GAAG,OAAO;AAAA,IACV,UAAU;AAAA,IACV,QAAQ;AAAA,EACT,CAAC;AAED,QAAM,yBAAyB,KAAK,aAAa,OAAO,YAAY;AAEpE,QAAM,qBAAqB,MAAM;AAChC,SAAK,MAAM;AACX,WAAO,MAAM;AAAA,EACd;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AAYO,SAAS,4BAUf,YACA,kBACA,OAMmG;AACnG,QAAM,SAAS,oBAAoB,YAAY,MAAM,WAAW;AAEhE,QAAM,EAAE,yBAAyB,IAAI;AAAA,IACpC,OAAO,OAAO;AAAA,IACd,MAAM;AAAA,EACP;AAEA,QAAM,OAAO,QAAwD;AAAA,IACpE,GAAG,OAAO;AAAA,IACV,UAAU;AAAA,IACV,QAAQ;AAAA,EACT,CAAC;AAED,QAAM,yBAAyB,KAAK,aAAa,OAAO,YAAY;AAEpE,QAAM,qBAAqB,MAAM;AAChC,SAAK,MAAM;AACX,WAAO,MAAM;AAAA,EACd;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":[]}