{"id":15366884,"url":"https://github.com/cupcakearmy/formhero","last_synced_at":"2025-04-15T12:33:28.335Z","repository":{"id":96682903,"uuid":"207488809","full_name":"cupcakearmy/formhero","owner":"cupcakearmy","description":"Fully customisable React form utility","archived":false,"fork":false,"pushed_at":"2023-10-18T23:10:34.000Z","size":541,"stargazers_count":33,"open_issues_count":4,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-28T20:12:12.655Z","etag":null,"topics":["form-helper","form-validation","forms","hooks","react"],"latest_commit_sha":null,"homepage":"https://cupcakearmy.github.io/formhero/","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/cupcakearmy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"cupcakearmy"}},"created_at":"2019-09-10T07:08:47.000Z","updated_at":"2023-12-22T08:28:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"4e3f9e01-bda8-478c-aac5-dc64d22367ef","html_url":"https://github.com/cupcakearmy/formhero","commit_stats":{"total_commits":115,"total_committers":3,"mean_commits":"38.333333333333336","dds":0.4956521739130435,"last_synced_commit":"d20c383d3d0519d70843847d2990e1f5fba4fc2d"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cupcakearmy%2Fformhero","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cupcakearmy%2Fformhero/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cupcakearmy%2Fformhero/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cupcakearmy%2Fformhero/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cupcakearmy","download_url":"https://codeload.github.com/cupcakearmy/formhero/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249072279,"owners_count":21208155,"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":["form-helper","form-validation","forms","hooks","react"],"created_at":"2024-10-01T13:20:09.193Z","updated_at":"2025-04-15T12:33:28.329Z","avatar_url":"https://github.com/cupcakearmy.png","language":"TypeScript","funding_links":["https://github.com/sponsors/cupcakearmy"],"categories":[],"sub_categories":[],"readme":"![Logo](https://raw.githubusercontent.com/cupcakearmy/formhero/master/.github/Logo.jpg)\n\n![version badge](https://badgen.net/npm/v/formhero)\n![types badge](https://badgen.net/npm/types/formhero)\n![downloads badge](https://badgen.net/npm/dt/formhero)\n![dependencies](https://badgen.net//bundlephobia/dependency-count/formhero)\n![minzip size badge](https://badgen.net/bundlephobia/minzip/formhero)\n\n**Fully customisable react form utility.**\n\n## 🌈 Features\n\n- Typescript compatible\n- Customizable extractor, validator, getter and setters. (More in the docs)\n- **0** Dependencies\n- Tiny **~0.7kB**\n- React Hooks\n\n###### Installation\n\n```\nnpm i formhero\n```\n\n_Note:_ Requires at least typescript version `3.5`, otherwise the error object will not have the right inherited types.\n\n### 👁 Demos\n\n- [**_Live Web_**](https://cupcakearmy.github.io/formhero/)\n- [**_Live Codesandbox_**](https://codesandbox.io/embed/formhero-simple-bdcx2?expanddevtools=1\u0026fontsize=14)\n- [**_Live React-Native_**](https://snack.expo.io/@cupcakearmy/useform)\n\n### 📖 Docs\n\n- [Examples](#-examples-more-here)\n  - [Validation](#validation)\n  - [Easy Customization](#easy-customization)\n  - [Dynamic Fields](#dynamic-fields)\n- [Docs](#-documentation)\n  - Contructor\n    - [Initial State](#initial)\n    - [Validators](#validators)\n    - [Options](#options)\n  - Returns\n    - [field](#field)\n    - [form](#form)\n    - [errors](#errors)\n    - [isValid](#isvalid)\n    - [setField](#setfield)\n    - [setForm](#setform)\n    - [setErrors](#seterrors)\n\n## 🤔 Motivation \u0026 Why\n\nSo why write yet another form utility you might ask? First off, I don't like the Formik approach. In my humble opition formik is very verbose and requires lots of boilerplate. Also does not work with hooks. [react-hook-form](https://react-hook-form.com/) is a very cool library and it is the main inspiration for formhero. It does almost everything right... typescript, no deps, small, concise.\n\nThe problem that I found while using it was that 3rd party ui libs like [Ant Design](https://ant.design/) or [Fabric UI](https://developer.microsoft.com/en-us/fabric#/controls/web) do not always have the standart `onChange` or `value` props in their components. That is where react-hook-form starts falling apart. This is what formhero tries to address in the most minimalistic way possible, with as little code as needed. All in pure typescript and no deps.\n\n## 🚀 Quickstart\n\n```typescript\nimport ReactDOM from 'react-dom'\nimport { useForm } from 'formhero'\n\nconst Form = () =\u003e {\n  const { field, form } = useForm({\n    username: '',\n    password: '',\n  })\n\n  const _submit = (e: React.FormEvent) =\u003e {\n    e.preventDefault()\n    console.log(form)\n  }\n\n  return (\n    \u003cdiv\u003e\n      \u003cform onSubmit={_submit}\u003e\n        \u003cinput {...field('username')} /\u003e\n        \u003cinput {...field('password')} /\u003e\n\n        \u003cbutton type=\"submit\"\u003eGo 🚀\u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n## 🔥 Examples [(More Here)](https://github.com/CupCakeArmy/formhero/tree/master/examples)\n\n### Validation\n\n```typescript\nconst Form = () =\u003e {\n  const { field, form, errors } = useForm(\n    {\n      username: '',\n      email: '',\n      password: '',\n    },\n    {\n      username: (value) =\u003e value.length \u003e 3,\n      email: {\n        validator: /@/,\n        message: 'Must contain an @',\n      },\n      password: [\n        {\n          validator: /[A-Z]/,\n          message: 'Must contain an uppercase letter',\n        },\n        {\n          validator: /[\\d]/,\n          message: 'Must contain a digit',\n        },\n      ],\n    }\n  )\n\n  return (\n    \u003cform\u003e\n      \u003ch1\u003eErrors \u0026 Validation\u003c/h1\u003e\n\n      \u003cinput {...field('username')} placeholder=\"Username\" /\u003e\n      {errors.username \u0026\u0026 'Must be longer than 3'}\n\n      \u003cinput {...field('email')} placeholder=\"EMail\" /\u003e\n      {errors.email}\n\n      \u003cinput {...field('password')} placeholder=\"Password\" type=\"password\" /\u003e\n      {errors.password}\n    \u003c/form\u003e\n  )\n}\n```\n\n### Easy Customization\n\nOften it happens that you use a specific input or framework, so the default getter, setter and extractor for the event won't cut it. No worries: formhero got you covered!\n\n```typescript\nconst Form = () =\u003e {\n  const { field, form, errors } = useForm({\n    awesome: true,\n  })\n\n  return (\n    \u003cform\n      onSubmit={(e) =\u003e {\n        e.preventDefault()\n        console.log(form)\n      }}\n    \u003e\n      \u003ch1\u003eCustom\u003c/h1\u003e\n\n      \u003clabel\u003e\n        \u003cinput\n          type=\"checkbox\"\n          {...field('awesome', {\n            setter: 'checked',\n            getter: 'onChange',\n            extractor: (e) =\u003e e.target.checked,\n          })}\n        /\u003e\n        Is it awesome?\n      \u003c/label\u003e\n\n      \u003cinput type=\"submit\" /\u003e\n    \u003c/form\u003e\n  )\n}\n```\n\n### Dynamic Fields\n\nSometimes you don't know all the fields upfront. You can simply define a generic type and assign it to the initial object. Of course type assistance is limited in this case as formhero cannot be sure what keys are valid.\n\n```typescript\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport { useForm } from 'formhero'\n\ntype MyForm = { [field: string]: string | number }\n\nconst init: MyForm = {\n  username: 'unicorn',\n  password: '',\n}\n\nconst Form: React.FC = () =\u003e {\n  const { field, form, errors } = useForm(init)\n\n  return (\n    \u003cform\u003e\n      \u003cinput {...field('username')} placeholder=\"Username\" /\u003e\n      \u003cinput {...field('someother')} placeholder=\"Something else\" /\u003e\n      \u003cinput {...field('password')} placeholder=\"Password\" type=\"password\" /\u003e\n    \u003c/form\u003e\n  )\n}\n```\n\n## 📖 Documentation\n\n### `useForm`\n\n```typescript\nconst { field, errors, update, form, isValid } = useForm(initial, validators, options)\n```\n\n### Initial\n\nThis is the base state of the form. Also the typescript types are inhered by this.\n\n###### Example\n\n```javascript\nconst initial = {\n  username: 'defaultValue',\n  password: '',\n  rememberMe: true,\n}\n```\n\n### Validators\n\nA validator is an object that taked in either a `RegExp` or a `Function` (can be async) or an array of those. Optionally you can pass a message string that will be displayed instead of the default one.\n\nA validator functions takes the current value as input and should return a `boolean` or a `string`. If returned `true` the input counts as valid, if `false` it's not. If you pass a string formhero will treat it as not valid and display the string returned as error message.\n\n###### Example: Regular Expression\n\n```javascript\nconst validators = {\n  // Only contains letters.\n  // This could also be a (also async) function that returns a boolean.\n  username: /^[A-z]*$/,\n}\n```\n\n###### Example: Function\n\n```typescript\nconst validators = {\n  username: (value: string) =\u003e value.lenght \u003e 3,\n}\n```\n\n###### Example: With Object\n\n```javascript\nconst validators = {\n  username: {\n    validator: /^[A-z]*$/,\n    message: 'My custom error message',\n  },\n}\n```\n\n###### Example: Multiple Validators\n\n```javascript\nconst validators = {\n  username: [\n    {\n      validator: /^[A-z]*$/,\n      message: 'My custom error message',\n    },\n    /[\\d]/,\n    async (value) =\u003e value.length \u003e 0,\n    {\n      validator: (value) =\u003e true,\n      message: 'Some other error',\n    },\n  ],\n}\n```\n\n###### Example: Dynamic Error Message\n\n```javascript\nconst validators = {\n  username: async (s: string) =\u003e {\n    const taken = await API.isUsernameTaken(s)\n    return taken ? 'Username is taken' : true\n  },\n}\n```\n\n### Options\n\nSometimes it's practical to have some different default values when using for example react-native or some other framework where the default `value`, `onChange` and `(e)=\u003e e.target.value` do not apply.\n\n###### Example: React Native (Method 1 - Global options)\n\n[Check the Expo Snack for a live preview](https://snack.expo.io/@cupcakearmy/useform)\n\n```javascript\nimport * as React from 'react'\nimport { Text, SafeAreaView, TextInput } from 'react-native'\nimport { useForm } from 'formhero'\n\nconst initial = {\n  username: 'i am all lowercase',\n}\nconst validators = {}\nconst options = {\n  setter: 'value', // This is not stricly necessarry as 'value' would already be the default.\n  getter: 'onChangeText',\n  extractor: (text) =\u003e text.toLowerCase(),\n}\n\nexport default () =\u003e {\n  const { form, field } = useForm(initial, validators, options)\n\n  return (\n    \u003cSafeAreaView\u003e\n      \u003cTextInput style={{ height: 40, borderColor: 'gray', borderWidth: 2 }} {...field('username')} /\u003e\n      \u003cText\u003e{form.username}\u003c/Text\u003e\n    \u003c/SafeAreaView\u003e\n  )\n}\n```\n\n###### Example: React Native (Method 2 - Local overwrite)\n\n```javascript\n// ...\n\nexport default () =\u003e {\n  const { form, field } = useForm({\n    username: 'i am all lowercase',\n  })\n\n  return (\n    \u003cSafeAreaView\u003e\n      \u003cTextInput\n        style={{ height: 40, borderColor: 'gray', borderWidth: 2 }}\n        {...field('username', {\n          setter: 'value', // This is not stricly necessarry as 'value' would already be the default.\n          getter: 'onChangeText',\n          extractor: (text) =\u003e text.toLowerCase(),\n        })}\n      /\u003e\n      \u003cText\u003e{form.username}\u003c/Text\u003e\n    \u003c/SafeAreaView\u003e\n  )\n}\n```\n\n### field\n\nThe `field` object is used to bind the form state to the input.\n\n###### Example: Simple\n\n```javascript\nconst { field } = useForm()\n\n\u003cinput {...field('username')} /\u003e\n```\n\n###### Example: With custom options\n\nAll are optional.\n\n```javascript\nconst { field } = useForm()\n\n\u003cinput {...field('username', {\n  getter: 'onChage',\n  setter: 'value',\n  extractor: (e) =\u003e e.target.value\n})} /\u003e\n```\n\n## Form\n\nThis is the form state that you can use when submitting the data\n\n###### Example\n\n```javascript\n\nconst { form } = useForm(...);\n\n// ...\n\n\u003cform onSubmit={()=\u003e console.log(form)}\u003e\n  // ...\n\u003c/form\u003e\n```\n\n## Errors\n\nThis object contains the error messages if a field is not valid.\nThe error message can be specified by you, otherwise it will return `Error in ${field}`\n\n###### Example\n\n```javascript\nconst { errors } = useForm(...)\n\n//...\n\n{errors.username}\n{errors.password}\n```\n\n## isValid\n\n`isValid` is a little simple helper that checks whether the `error` object is clear or if there are errors left.\n\n## setField\n\nThe `setField` function allows you to manually change and assign the state of a field. The type of the field must be the same as the initial type given in the constructor.\n\n###### Example\n\n```javascript\nconst { form, setField } = useForm(...)\n\nconst resetUsername = () =\u003e {\n  setField('username', 'new value')\n}\n```\n\n## setForm\n\nThe `setForm` function allows you to manually change and assign the state of the form. This can be usefull when you want to reset a field or the whole form. The input must have the same type as the initial state.\n\n###### Example\n\n```javascript\nconst initial = {username: '', password: ''}\n\nconst { form, setForm } = useForm(initial, ...)\n\nconst resetForm = () =\u003e {\n  setForm(initial)\n}\n```\n\n## setErrors\n\nThe `setErrors` function allows you to manually change and assign the state of the errors. This can be usefull when you want to set an error manually (e.g. sent from the server).\n\n###### Example\n\n```javascript\n\nconst { form, setErrors } = useForm(...)\n\nconst setError = () =\u003e {\n  setErrors({username: 'Already taken'})\n}\n```\n\n### Thanks \u0026 Attributions\n\n- Thanks for [brendanmckenzie](https://github.com/brendanmckenzie) for suggesting to change `auto` to `field`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcupcakearmy%2Fformhero","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcupcakearmy%2Fformhero","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcupcakearmy%2Fformhero/lists"}