{"id":13395249,"url":"https://github.com/robinweser/react-controlled-form","last_synced_at":"2025-04-05T12:05:34.577Z","repository":{"id":40295425,"uuid":"91783120","full_name":"robinweser/react-controlled-form","owner":"robinweser","description":"React Forms with Zod Validation","archived":false,"fork":false,"pushed_at":"2024-11-18T22:43:18.000Z","size":804,"stargazers_count":120,"open_issues_count":2,"forks_count":10,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-29T11:07:37.333Z","etag":null,"topics":["controlled-form","form","form-validation","forms","react","react-forms","zod","zod-validation"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robinweser.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2017-05-19T08:19:20.000Z","updated_at":"2025-03-14T09:07:15.000Z","dependencies_parsed_at":"2024-06-04T11:54:29.379Z","dependency_job_id":"ad01eb8e-b2d4-49a6-b495-40af182c92c5","html_url":"https://github.com/robinweser/react-controlled-form","commit_stats":{"total_commits":101,"total_committers":7,"mean_commits":"14.428571428571429","dds":0.4158415841584159,"last_synced_commit":"8900f2b09d86af4d290a2833e8a74743daa47e69"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinweser%2Freact-controlled-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinweser%2Freact-controlled-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinweser%2Freact-controlled-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinweser%2Freact-controlled-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robinweser","download_url":"https://codeload.github.com/robinweser/react-controlled-form/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247332604,"owners_count":20921853,"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":["controlled-form","form","form-validation","forms","react","react-forms","zod","zod-validation"],"created_at":"2024-07-30T17:01:47.671Z","updated_at":"2025-04-05T12:05:34.548Z","avatar_url":"https://github.com/robinweser.png","language":"TypeScript","funding_links":[],"categories":["Others","TypeScript"],"sub_categories":[],"readme":"# react-controlled-form\n\nA package for creating controlled forms in React with baked in [zod](https://zod.dev) validation.\u003cbr /\u003e\nYou own and control the rendered markup and the hook takes care of the state and validation.\n\n\u003cimg alt=\"npm version\" src=\"https://badge.fury.io/js/react-controlled-form.svg\"\u003e \u003cimg alt=\"npm downloads\" src=\"https://img.shields.io/npm/dm/react-controlled-form.svg\"\u003e \u003ca href=\"https://bundlephobia.com/result?p=react-controlled-form@latest\"\u003e\u003cimg alt=\"Bundlephobia\" src=\"https://img.shields.io/bundlephobia/minzip/react-controlled-form.svg\"\u003e\u003c/a\u003e\n\n## Installation\n\n```sh\n# npm\nnpm i --save react-controlled-form\n# yarn\nyarn add react-controlled-form\n# pnpm\npnpm add react-controlled-form\n```\n\n## The Gist\n\n```tsx\nimport * as React from 'react'\nimport { useForm, FieldProps } from 'react-controlled-form'\nimport { z, ZodError } from 'zod'\n\n// create our schema with validation included\nconst Z_RegisterInput = z.object({\n  name: z.string().optional(),\n  email: z.string().email(),\n  // we can also pass custom messages as a second parameter\n  password: z\n    .string()\n    .min(8, { message: 'Your password next to have at least 8 characters.' }),\n})\n\ntype T_RegisterInput = z.infer\u003ctypeof Z_RegisterInput\u003e\n\nfunction Form() {\n  // we create a form by passing the schema\n  const { useField, handleSubmit, formProps, reset } = useForm(Z_RegisterInput)\n\n  // now we can create our fields for each property\n  // the field controls the state and validation per property\n  const name = useField('name')\n  const email = useField('email')\n  const password = useField('password')\n\n  function onSuccess(data: T_RegisterInput) {\n    // do something with the safely parsed data\n    console.log(data)\n    // reset the form to its initial state\n    reset()\n  }\n\n  function onFailure(error: ZodError) {\n    console.error(error)\n  }\n\n  return (\n    \u003cform {...formProps} onSubmit={handleSubmit(onSuccess, onFailure)}\u003e\n      \u003clabel htmlFor=\"name\"\u003eFull Name\u003c/label\u003e\n      \u003cinput id=\"name\" {...name.inputProps} /\u003e\n\n      \u003clabel htmlFor=\"email\"\u003eE-Mail\u003c/label\u003e\n      \u003cinput id=\"email\" type=\"email\" {...email.inputProps} /\u003e\n      \u003cp style={{ color: 'red' }}\u003e{email.errorMessage}\u003c/p\u003e\n\n      \u003clabel htmlFor=\"password\"\u003ePassword\u003c/label\u003e\n      \u003cinput id=\"password\" type=\"password\" {...password.inputProps} /\u003e\n      \u003cp style={{ color: 'red' }}\u003e{password.errorMessage}\u003c/p\u003e\n\n      \u003cbutton type=\"submit\"\u003eLogin\u003c/button\u003e\n    \u003c/form\u003e\n  )\n}\n```\n\n\u003e **Note**: This is, of course, a simplified version and you most likely render custom components to handle labelling, error messages and validation styling.\u003cbr /\u003eFor such cases, each field also exposes a `props` property that extends the `inputProps` with non-standard HTML attributes.\n\n## API Reference\n\n### useForm\n\nThe core API that connects the form with a zod schema and returns a set of helpers to manage the state and render the actual markup.\n\n| Parameter          |  Type                                        | Default                    |  Description                                       |\n| ------------------ | -------------------------------------------- | -------------------------- | -------------------------------------------------- |\n| schema             | ZodObject                                    |                            | A valid zod object schema                          |\n| formatErrorMessage |  `(error: ZodIssue, name: string) =\u003e string` | `(error) =\u003e error.message` | A custom formatter that receives the raw zod issue |\n\n```ts\nimport { z } from 'zod'\n\nconst Z_Input = z.object({\n  name: z.string().optional(),\n  email: z.string().email(),\n  // we can also pass custom messages as a second parameter\n  password: z\n    .string()\n    .min(8, { message: 'Your password next to have at least 8 characters.' }),\n})\n\ntype T_Input = z.infer\u003ctypeof Z_Input\u003e\n\n// usage inside react components\nconst { useField, handleSubmit, reset, formProps } = useForm(Z_Input)\n```\n\n#### formatErrorMessage\n\nThe preferred way to handle custom error messages would be to add them to the schema directly.\u003cbr /\u003e\nIn some cases e.g. when receiving the schema from an API or when having to localise the error, we can leverage this helper.\n\n```ts\nimport { ZodIssue } from 'zod'\n\n// Note: the type is ZodIssue and not ZodError since we always only show the first error\nfunction formatErrorMessage(error: ZodIssue, name: string) {\n  switch (error.code) {\n    case 'too_small':\n      return `This field ${name} requires at least ${error.minimum} characters.`\n    default:\n      return error.message\n  }\n}\n```\n\n### useField\n\nA hook that manages the field state and returns the relevant HTML attributes to render our inputs.\u003cbr /\u003e\nAlso returns a set of helpers to manually update and reset the field.\n\n| Parameter |  Type                          | Default               |  Description                                                |\n| --------- | ------------------------------ | --------------------- | ----------------------------------------------------------- |\n| name      | `keyof z.infer\u003ctypeof schema\u003e` |                       | The name of the schema property that this field connects to |\n| config    | [Config](#config)              | See [Config](#config) | Initial field data and additional config options            |\n\n#### Config\n\n| Property         | Type                                 | Default                 |  Description                                                                                                                |\n| ---------------- | ------------------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- |\n| value            | `any`                                | `''`                    | Initial value                                                                                                               |\n| disabled         | `boolean`                            | `false`                 | Initial disabled state                                                                                                      |\n| touched          | `boolean`                            | `false`                 | Initial touched state that indicates whether validation errors are shown or not                                             |\n| showValidationOn | `\"change\"` \\| `\"blur\"` \\| `\"submit\"` | `\"submit\"`              | Which event is used to trigger the touched state                                                                            |\n| parseValue       | `(Event) =\u003e any`                     | `(e) =\u003e e.target.value` | How the value is received from the input element.\u003cbr /\u003eUse `e.target.checked` when working with `\u003cinput type=\"checkbox\" /\u003e` |\n\n```ts\nconst { inputProps, props, errorMessage, update, reset } = useField('email')\n```\n\n#### inputProps\n\nPass these to native HTML `input`, `select` and `textarea` elements.\u003cbr /\u003e\nUse `data-valid` to style the element based on the validation state.\n\n```ts\ntype InputProps = {\n  name: string\n  value: any\n  disabled: boolean\n  'data-valid': boolean\n  onChange: React.ChangeEventHandler\u003cHTMLElement\u003e\n  onBlur?: React.KeyboardEventHandler\u003cHTMLElement\u003e\n}\n```\n\n#### props\n\nPass these to custom components that render label and input elements.\u003cbr /\u003e\nAlso includes information such as `errorMessage` or `valid` that's non standard HTML attributes and thus can't be passed to native HTML `input` elements directly.\n\n```ts\ntype Props = {\n  value: any\n  name: string\n  valid: boolean\n  required: boolean\n  disabled: boolean\n  errorMessage?: string\n  onChange: React.ChangeEventHandler\u003cHTMLElement\u003e\n  onBlur?: React.KeyboardEventHandler\u003cHTMLElement\u003e\n}\n```\n\n#### errorMessage\n\n\u003e **Note**: If you're using [`props`](#props), you already get the errorMessage!\n\nA string containing the validation message. Only returned if the field is invalid **and** touched.\n\n#### update\n\nProgrammatically change the data of a field. Useful e.g. when receiving data from an API.\u003cbr /\u003e\nIf value is changed, it will automatically trigger re-validation.\n\n\u003e **Note**: If you know the initial data upfront, prefer to pass it to the `useField` hook directly though.\n\n```ts\nupdate({\n  value: 'Foo',\n  touched: true,\n})\n```\n\n#### reset\n\nResets the field back to its initial field data.\n\n```ts\nreset()\n```\n\n### handleSubmit\n\nHelper that wraps the native `onSubmit` event on `\u003cform\u003e` elements.\u003cbr /\u003e\nIt prevents default action execution and parses the form data using the zod schema.\n\n| Parameter |  Type                            |  Description                                       |\n| --------- | -------------------------------- | -------------------------------------------------- |\n| onSuccess | `(data: z.infer\u003ctypeof schema\u003e)` | Callback on successful safe parse of the form data |\n| onFailure | `(error: ZodError)`              | Callback on failed safe parse                      |\n\n```ts\nimport { ZodError } from 'zod'\n\nfunction onSuccess(data: T_Input) {\n  console.log(data)\n}\n\nfunction onFailure(error: ZodError) {\n  console.error(error)\n}\n\n// \u003cform\u003e onSubmit handler\nconst onSubmit = handleSubmit(onSuccess, onFailure)\n```\n\n### reset\n\nResets the form fields back to their initial field data. Helpful when trying to clear a form after a successful submit.\n\n\u003e **Note**: This API is similar to the `reset` helper that the `useField` hook returns. The only difference is that it resets all fields.\n\n```\nreset()\n```\n\n### isDirty\n\nReturns whether the form is dirty, meaning that any of the fields was altered compared to their initial state.\u003cbr /\u003e\nUseful e.g. when conditionally showing a save button or when you want to inform a user that he's closing a modal with unsafed changes.\n\n```ts\nisDirty()\n```\n\n### formProps\n\nAn object that contains props that are passed to the native `\u003cform\u003e` element.\nCurrently only consists of a single prop:\n\n```ts\nconst formProps = {\n  noValidate: true,\n}\n```\n\n## Recipes\n\n### Non-String Values\n\nBy default, [useField](#usefield) expects string values and defaults to an empty string if no initial value is provided.\u003cbr /\u003e\nIn order to also support e.g. `boolean` values or arrays, we can customise the types and pass new values.\n\n```tsx\nimport { ChangeEvent } from 'react'\n\nconst acceptsTerms = useField\u003cboolean, ChangeEvent\u003cHTMLInputElement\u003e\u003e('terms', {\n  // alter how the value is obtained if neccessary\n  // e.g. for checkboxes or custom inputs\n  parseValue: (e) =\u003e e.target.checked,\n  // set an initial value overwritting the default empty string\n  value: false,\n})\n\n// custom multi-select input that returns an array of values on change\ntype Tags = Array\u003cstring\u003e\ntype TagsChangeEvent = (value: Tags) =\u003e void\n\nconst tags = useField\u003cTags, TagsChangeEvent\u003e('tags', {\n  parseValue: (value) =\u003e value,\n  value: [],\n})\n```\n\nPassing a custom value type and change event will also change the type of `field.value` and the expected input for [update](#update).\n\n## License\n\nreact-controlled-form is licensed under the [MIT License](http://opensource.org/licenses/MIT).\u003cbr\u003e\nDocumentation is licensed under [Creative Common License](http://creativecommons.org/licenses/by/4.0/).\u003cbr\u003e\nCreated with ♥ by [@robinweser](http://weser.io) and all the great contributors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobinweser%2Freact-controlled-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobinweser%2Freact-controlled-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobinweser%2Freact-controlled-form/lists"}