{"id":14957303,"url":"https://github.com/next-safe-action/adapter-react-hook-form","last_synced_at":"2025-04-07T06:04:14.979Z","repository":{"id":253330051,"uuid":"843153038","full_name":"next-safe-action/adapter-react-hook-form","owner":"next-safe-action","description":"Seamlessly integrate next-safe-action with react-hook-form.","archived":false,"fork":false,"pushed_at":"2025-04-02T20:41:04.000Z","size":235,"stargazers_count":62,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-02T21:33:06.270Z","etag":null,"topics":["forms","next","next-safe-action","nextjs","react","react-hook-form","server-actions"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@next-safe-action/adapter-react-hook-form","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/next-safe-action.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-15T22:33:49.000Z","updated_at":"2025-04-02T20:32:10.000Z","dependencies_parsed_at":"2024-09-08T17:19:40.193Z","dependency_job_id":"8a3e0bbb-cfa5-43f3-a5aa-8e3e795eccfc","html_url":"https://github.com/next-safe-action/adapter-react-hook-form","commit_stats":{"total_commits":27,"total_committers":1,"mean_commits":27.0,"dds":0.0,"last_synced_commit":"163b6a8a2af94a52d1f6e997a29a89e408f09ee9"},"previous_names":["next-safe-action/adapter-react-hook-form"],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/next-safe-action%2Fadapter-react-hook-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/next-safe-action%2Fadapter-react-hook-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/next-safe-action%2Fadapter-react-hook-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/next-safe-action%2Fadapter-react-hook-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/next-safe-action","download_url":"https://codeload.github.com/next-safe-action/adapter-react-hook-form/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247601447,"owners_count":20964864,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["forms","next","next-safe-action","nextjs","react","react-hook-form","server-actions"],"created_at":"2024-09-24T13:14:39.191Z","updated_at":"2025-04-07T06:04:14.949Z","avatar_url":"https://github.com/next-safe-action.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/TheEdoRan/next-safe-action/main/assets/logo.png\" alt=\"next-safe-action logo\" width=\"36\" height=\"36\"\u003e\n  \u003ca href=\"https://github.com/next-safe-action/adapter-react-hook-form\"\u003e\u003ch1\u003eadapter-react-hook-form\u003c/h1\u003e\u003c/a\u003e\n\u003c/div\u003e\n\nThis adapter offers a way to seamlessly integrate [next-safe-action](https://github.com/TheEdoRan/next-safe-action) with [react-hook-form](https://github.com/react-hook-form/react-hook-form).\n\n# Requirements\n\n- React \u003e= `18.2.0`\n- Next.js \u003e= `14.0.0`\n- next-safe-action \u003e= `7.6.0`\n- react-hook-form \u003e= `7.0.0`\n- @hookform/resolvers \u003e= `3.0.0`\n\n# Installation\n\n```sh\nnpm i next-safe-action react-hook-form @hookform/resolvers @next-safe-action/adapter-react-hook-form\n```\n\n# Example\n\nThe best way to learn how to use this adapter is to take a look at the examples. The [app](https://github.com/next-safe-action/adapter-react-hook-form/tree/main/apps/example) in this repository shows you how to use the `useHookFormAction` and `useHookFormOptimisticAction` hooks:\n- [`useHookFormAction` example \\(login\\)](https://github.com/next-safe-action/adapter-react-hook-form/tree/main/apps/example/src/app/login)\n- [`useHookFormOptimisticAction` example \\(todos\\)](https://github.com/next-safe-action/adapter-react-hook-form/tree/main/apps/example/src/app/todos)\n\n# Hooks\n\n## `useHookFormAction`\n\nThis hook is a wrapper around `useAction` from next-safe-action and `useForm` from react-hook-form that makes it much easier to use safe actions with react-hook-form. It also maps validation errors to `FieldErrors` compatible with react-hook-form.\n\n### Example (login)\n\n1. First of all, we need a shared file to store our validation schema(s). In this case, the `loginSchema` Zod validator is exported from `validation.ts`:\n\n```ts\nimport { z } from \"zod\";\n\nexport const loginSchema = z.object({\n  username: z.string().min(3).max(30),\n  password: z.string().min(8).max(100),\n});\n```\n\n2. Then, we can create our login action using `loginSchema`:\n\n```ts\n\"use server\";\n\nimport { returnValidationErrors } from \"next-safe-action\";\nimport { actionClient } from \"@/lib/safe-action\";\nimport { loginSchema } from \"./validation\";\nimport { checkCredentials } from \"@/services/auth\";\n\nexport const loginAction = actionClient\n  .schema(loginSchema)\n  .action(async ({ parsedInput }) =\u003e {\n    const valid = await checkCredentials(\n      parsedInput.username,\n      parsedInput.password\n    );\n\n    // If the credentials are invalid, return root validation error.\n    if (!valid) {\n      returnValidationErrors(loginSchema, {\n        _errors: [\"Invalid username or password\"],\n      });\n    }\n\n    return {\n      successful: true,\n    };\n  });\n```\n\n3. Finally, we can use `useHookFormAction` in our Client Component, by passing to it the `loginSchema` and `loginAction` declared above:\n\n```tsx\n\"use client\";\n\nimport { useHookFormAction } from \"@next-safe-action/adapter-react-hook-form/hooks\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { loginSchema } from \"./validation\";\nimport { loginAction } from \"./login-action\";\n\nexport function LoginForm() {\n  const { form, action, handleSubmitWithAction, resetFormAndAction } =\n    useHookFormAction(loginAction, zodResolver(loginSchema), {\n      actionProps: {},\n      formProps: {},\n      errorMapProps: {}\n    });\n\n  return \u003cform onSubmit={handleSubmitWithAction}\u003e...\u003c/form\u003e;\n}\n```\n\n### Parameters\n\n- `safeAction`: the safe action (required)\n- `hookFormResolver`: a react-hook-form validation resolver (required)\n- `props`: props for `useAction`, `useForm` and error mapper (optional)\n\n### Return values (object)\n\n- `form`: the react-hook-form form\n- `action`: the next-safe-action action\n- `handleSubmitWithAction`: a function that handles form submission by automatically executing the action\n- `resetFormAndAction`: a function that resets the form and the action state\n\n## `useHookFormOptimisticAction`\n\nThis hook is a wrapper around `useOptimisticAction` from next-safe-action and `useForm` from react-hook-form that makes it much easier to use safe actions with react-hook-form. It also maps validation errors to `FieldErrors` compatible with react-hook-form.\n\n### Example (add todo)\n\n1. First of all, we need a shared file to store our validation schema(s). In this case, the `addTodoSchema` Zod validator is exported from `validation.ts`:\n\n```ts\nimport { z } from \"zod\";\n\nexport const addTodoSchema = z.object({\n  newTodo: z.string().min(1).max(200),\n});\n```\n\n2. Then, we can create our add todo action using `addTodoSchema`:\n\n```ts\n\"use server\";\n\nimport { returnValidationErrors } from \"next-safe-action\";\nimport { revalidatePath } from \"next/cache\";\nimport { actionClient } from \"@/lib/safe-action\";\nimport { addTodoSchema } from \"./validation\";\nimport { badWordsCheck } from \"@/utils\";\nimport { saveTodoInDb } from \"@/services/db\";\n\nexport const addTodoAction = actionClient\n  .schema(addTodoSchema)\n  .action(async ({ parsedInput }) =\u003e {\n    const containsBadWords = badWordsCheck(parsedInput.newTodo)\n\n    // If the todo con\n    if (containsBadWords) {\n      returnValidationErrors(addTodoSchema, {\n        newTodo: {\n          _errors: [\"The todo contains bad words!\"],\n        }\n      });\n    }\n\n    await saveTodoInDb(parsedInput.newTodo);\n    revalidatePath(\"/\");\n\n    return {\n      newTodo: parsedInput.newTodo,\n    };\n  });\n```\n\n3. Finally, we can use `useHookFormOptimisticAction` in our Client Component, by passing to it the `addTodoSchema` and `addTodoAction` declared above:\n\n```tsx\n\"use client\";\n\nimport { useHookFormOptimisticAction } from \"@next-safe-action/adapter-react-hook-form/hooks\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { addTodoSchema } from \"./validation\";\nimport { addTodoAction } from \"./addtodo-action\";\n\ntype Props = {\n  todos: string[];\n};\n\n// Todos are passed from the parent Server Component and updated each time a new todo is added\n// thanks to the `revalidatePath` function called inside the action.\nexport function AddTodoForm({ todos }: Props) {\n  const { form, action, handleActionSubmit, resetFormAndAction } =\n    useHookFormOptimisticAction(addTodoAction, zodResolver(addTodoSchema), {\n      actionProps: {\n        currentState: {\n          todos,\n        },\n        updateFn: (state, input) =\u003e {\n          return {\n            todos: [...state.todos, input.newTodo],\n          };\n        },\n      },\n      formProps: {},\n      errorMapProps: {},\n    });\n\n  return \u003cform onSubmit={handleActionSubmit}\u003e\u003c/form\u003e;\n}\n```\n\n### Parameters\n\n- `safeAction`: the safe action (required)\n- `hookFormResolver`: a react-hook-form validation resolver (required)\n- `props`: props for `useOptimisticAction`, `useForm` and error mapper. `actionProps.currentState` and `actionProps.updateFn` are required by the `useOptimisticAction` hook used under the hood, the rest are optional. (required/optional)\n\n### Return values (object)\n\n- `form`: the react-hook-form form\n- `action`: the next-safe-action action\n- `handleSubmitWithAction`: a function that handles form submission by automatically executing the action\n- `resetFormAndAction`: a function that resets the form and the action state\n\n## `useHookFormActionErrorMapper`\n\nFor more control over the execution flow, you can use this hook to get back the memoized mapped validation errors of the action. It can be useful for cases when you need to use both `useAction` and `useForm` in your Client Component, for a particular task, or when you want to create custom hooks.\n\n### Example (Client Component)\n\n1. We'll reuse the `loginSchema` and `loginAction` from the `useHookFormAction` example  here.\n\n2. Here's how you would use `useHookFormActionErrorMapper` in your Client Component:\n\n```tsx\n\"use client\";\n\nimport { useHookFormActionErrorMapper } from \"@next-safe-action/adapter-react-hook-form/hooks\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { loginSchema } from \"./validation\";\nimport { loginAction } from \"./login-action\";\nimport { useAction } from \"next-safe-action/hooks\";\nimport { useForm } from \"react-hook-form\";\nimport { Infer } from \"next-safe-action/adapters/types\";\n\nexport function CustomForm() {\n  const action = useAction(loginAction);\n\n  const { hookFormValidationErrors } = useHookFormActionErrorMapper\u003c\n    typeof loginSchema\n  \u003e(action.result.validationErrors, { joinBy: \"\\n\" });\n\n  const form = useForm\u003cInfer\u003ctypeof loginSchema\u003e\u003e({\n    resolver: zodResolver(loginSchema),\n    errors: hookFormValidationErrors,\n  });\n\n  return \u003cform onSubmit={form.handleSubmit(action.executeAsync)}\u003e...\u003c/form\u003e;\n}\n```\n\n### Parameters\n\n- `validationErrors`: next-safe-action object of `ValidationErrors`, or `undefined` (required)\n- `props`: `joinBy` from `ErrorMapperProps` type. It's used to determine how to join the error messages, if more than one is present in the errors array. It defaults to `\" \"`  (optional)\n\n### Return values (object)\n- `hookFormValidationErrors`: object of mapped errors with `FieldErrors` type, compatible with react-hook-form\n\n# Utilities\n\n## `mapToHookFormErrors`\n\nFor more advanced stuff, you can directly use the `mapToHookFormErrors` function that is utilized under the hood to map next-safe-action `ValidationErrors` to react-hook-form compatible `FieldErrors`.\n\n### Example\n\n```typescript\nimport { mapToHookFormErrors } from \"@next-safe-action/adapter-react-hook-form\";\nimport { loginAction } from \"./login-action\";\nimport type { loginSchema } from \"./validation\";\n\nasync function advancedStuff() {\n  const result = await loginAction({ username: \"foo\", password: \"bar\" });\n  const hookFormValidationErrors = mapToHookFormErrors\u003c\n    typeof loginSchema\n  \u003e(result?.validationErrors, { joinBy: \"\\n\" });\n\n  // Do something with `hookFormValidationErrors`...\n}\n```\n\n### Parameters\n\n- `validationErrors`: next-safe-action object of `ValidationErrors`, or `undefined` (required)\n- `props`: `joinBy` from `ErrorMapperProps` type. It's used to determine how to join the error messages, if more than one is present in the errors array. It defaults to `\" \"`  (optional)\n\n### Return value\n- mapped errors: object of mapped errors with `FieldErrors` type, compatible with react-hook-form\n\n# Types\n\n## `/`\n\n### `ErrorMapperProps`\n\nProps for `mapToHookFormErrors`. Also used by the hooks.\n\n```typescript\nexport type ErrorMapperProps = {\n  joinBy?: string;\n};\n```\n\n## `/hooks`\n\n### `HookProps`\n\nOptional props for `useHookFormAction` and `useHookFormOptimisticAction`.\n\n```typescript\nexport type HookProps\u003c\n  ServerError,\n  S extends Schema | undefined,\n  BAS extends readonly Schema[],\n  CVE,\n  CBAVE,\n  Data,\n  FormContext = any,\n\u003e = {\n  errorMapProps?: ErrorMapperProps;\n  actionProps?: HookBaseUtils\u003cS\u003e \u0026 HookCallbacks\u003cServerError, S, BAS, CVE, CBAVE, Data\u003e;\n  formProps?: Omit\u003cUseFormProps\u003cS extends Schema ? Infer\u003cS\u003e : any, FormContext\u003e, \"resolver\"\u003e;\n};\n```\n\n### `UseHookFormActionHookReturn`\n\nType of the return object of the `useHookFormAction` hook.\n\n```typescript\nexport type UseHookFormActionHookReturn\u003c\n  ServerError,\n  S extends Schema | undefined,\n  BAS extends readonly Schema[],\n  CVE,\n  CBAVE,\n  Data,\n  FormContext = any,\n\u003e = {\n  action: UseActionHookReturn\u003cServerError, S, BAS, CVE, CBAVE, Data\u003e;\n  form: UseFormReturn\u003cS extends Schema ? Infer\u003cS\u003e : any, FormContext\u003e;\n  handleSubmitWithAction: (e?: React.BaseSyntheticEvent) =\u003e Promise\u003cvoid\u003e;\n  resetFormAndAction: () =\u003e void;\n};\n```\n\n### `UseHookFormOptimisticActionHookReturn`\n\nType of the return object of the `useHookFormOptimisticAction` hook.\n\n```typescript\nexport type UseHookFormOptimisticActionHookReturn\u003c\n  ServerError,\n  S extends Schema | undefined,\n  BAS extends readonly Schema[],\n  CVE,\n  CBAVE,\n  Data,\n  State,\n  FormContext = any,\n\u003e = Omit\u003cUseHookFormActionHookReturn\u003cServerError, S, BAS, CVE, CBAVE, Data, FormContext\u003e, \"action\"\u003e \u0026 {\n  action: UseOptimisticActionHookReturn\u003cServerError, S, BAS, CVE, CBAVE, Data, State\u003e;\n};\n```\n\n## Infer types\n\nYou can use these utility types exported from the `/hooks` path to infer the return types of the hooks.\n\n### `InferUseHookFormActionHookReturn`\n\nInfer the type of the return object of the `useHookFormAction` hook.\n\n```typescript\nexport type InferUseHookFormActionHookReturn\u003cT extends Function, FormContext = any\u003e =\n  T extends SafeActionFn\u003c\n    infer ServerError,\n    infer S extends Schema | undefined,\n    infer BAS extends readonly Schema[],\n    infer CVE,\n    infer CBAVE,\n    infer Data\n  \u003e\n    ? UseHookFormActionHookReturn\u003cServerError, S, BAS, CVE, CBAVE, Data, FormContext\u003e\n    : never;\n```\n\n### `InferUseHookFormOptimisticActionHookReturn`\n\nInfer the type of the return object of the `useHookFormOptimisticAction` hook.\n\n```typescript\nexport type InferUseHookFormOptimisticActionHookReturn\u003cT extends Function, State, FormContext = any\u003e =\n  T extends SafeActionFn\u003c\n    infer ServerError,\n    infer S extends Schema | undefined,\n    infer BAS extends readonly Schema[],\n    infer CVE,\n    infer CBAVE,\n    infer Data\n  \u003e\n    ? UseHookFormOptimisticActionHookReturn\u003cServerError, S, BAS, CVE, CBAVE, Data, State, FormContext\u003e\n    : never;\n```\n\n# License\n\nThis project is released under the [MIT License](https://github.com/next-safe-action/adapter-react-hook-form/blob/main/LICENSE).\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnext-safe-action%2Fadapter-react-hook-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnext-safe-action%2Fadapter-react-hook-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnext-safe-action%2Fadapter-react-hook-form/lists"}