{"id":13499704,"url":"https://github.com/zero-plus-x/neoform","last_synced_at":"2025-03-29T05:32:10.113Z","repository":{"id":57142633,"uuid":"78037023","full_name":"zero-plus-x/neoform","owner":"zero-plus-x","description":":white_check_mark: React form state management and validation","archived":true,"fork":false,"pushed_at":"2019-06-21T13:12:28.000Z","size":656,"stargazers_count":163,"open_issues_count":2,"forks_count":7,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-24T02:09:37.774Z","etag":null,"topics":["form","form-validation","higher-order-component","hoc","react","validation"],"latest_commit_sha":null,"homepage":"https://www.webpackbin.com/bins/-KrbNqAfDYNwm07UmzTb","language":"JavaScript","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/zero-plus-x.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-04T17:22:32.000Z","updated_at":"2024-06-19T06:38:02.000Z","dependencies_parsed_at":"2022-09-05T18:41:30.242Z","dependency_job_id":null,"html_url":"https://github.com/zero-plus-x/neoform","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero-plus-x%2Fneoform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero-plus-x%2Fneoform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero-plus-x%2Fneoform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zero-plus-x%2Fneoform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zero-plus-x","download_url":"https://codeload.github.com/zero-plus-x/neoform/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246145012,"owners_count":20730494,"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","form-validation","higher-order-component","hoc","react","validation"],"created_at":"2024-07-31T22:00:38.887Z","updated_at":"2025-03-29T05:32:09.795Z","avatar_url":"https://github.com/zero-plus-x.png","language":"JavaScript","funding_links":[],"categories":["Uncategorized"],"sub_categories":["Uncategorized"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./logo.png\" width=\"300\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://travis-ci.org/zero-plus-x/neoform\"\u003e\u003cimg src=\"https://img.shields.io/travis/zero-plus-x/neoform/master.svg?style=flat-square\" alt=\"travis\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://codecov.io/github/zero-plus-x/neoform\"\u003e\u003cimg src=\"https://img.shields.io/codecov/c/github/zero-plus-x/neoform/master.svg?style=flat-square\" alt=\"travis\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nBetter form state management for React where data state is directly mapped to form fields, so form becomes just a representation and changing interface for that data state.\n\n* [Usage](#usage)\n  * [Intro](#intro)\n  * [`field`](#field)\n  * [`form`](#form)\n  * [App](#app)\n    * [`getValue`](#getvalue)\n    * [`setValue`](#setvalue)\n  * [Validation](#validation)\n    * [`fieldValidation`](#fieldvalidation)\n    * [`formValidation`](#formvalidation)\n    * [Validators](#validators)\n* [FAQ](#faq)\n* [Status](#status)\n* [Development](#development)\n\n## Usage\n\n### Intro\n\nLet's say you have some data and you want to represent it as an HTML form with an Input for each data field.\n\n```json\n\"user\": {\n  \"name\": \"Pepe\",\n  \"status\": \"sad\",\n  \"friends\": [\n    \"darkness\"\n  ]\n}\n```\n\nEach data field can be referenced with a \"key\" or \"property\" path. You might be familiar with this concept from working with immutable data structures or helpers like `lodash.get()`.\n\n```js\n\"user\": {\n  \"name\": \"Pepe\",  // \"user.name\"\n  \"status\": \"sad\", // \"user.status\"\n  \"friends\": [\n    \"darkness\"     // \"user.friends.0\"\n  ]\n}\n```\n\nThe first core idea of NeoForm is to map data to form fields using these key/property paths. We'll refer to this data as \"form state\" below.\n\nLet's see how it works with a step-by-step example. First, we need to install the following set of dependencies:\n\n```\nyarn add prop-types recompose neoform neoform-validation neoform-plain-object-helpers\n```\n\nWe'll start with creating a simple input:\n\n### `field`\n\n```js\nconst MyInput = () =\u003e (\n  \u003cinput/\u003e\n);\n\nexport default MyInput;\n```\n\nAfter wrapping this input with `field` [HOC](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) from NeoForm we'll have:\n\n#### `value` and `onChange` props\n\nA `value` from a form state (can be used in checkbox as a `checked` attribute if it's boolean, and so on) and `onChange` handler to let NeoForm know that value should be changed:\n\n```js\nimport { field } from 'neoform';\n\nconst MyInput = ({ value, onChange }) =\u003e (\n  \u003cinput\n    value={value}\n    onChange={(e) =\u003e onChange(e.target.value)}\n  /\u003e\n);\n\nexport default field(MyInput);\n```\n\nUse `(e) =\u003e e.target.checked` if you have a checkbox or just `(value) =\u003e value` if you have some custom/3rd-party field implementation.\n\n### `form`\n\nNow when the input is ready we can use it in a form:\n\n```js\nimport MyInput from '../MyInput';\n\nconst MyForm = () =\u003e (\n  \u003cform\u003e\n    \u003cMyInput name=\"user.name\"/\u003e\n    \u003cMyInput name=\"user.status\"/\u003e\n    \u003cMyInput name=\"user.friends.0\"/\u003e\n  \u003c/form\u003e\n);\n\nexport default MyForm;\n```\n\nLet's connect this form to NeoForm by wrapping it with a `form` HOC:\n\n```js\nimport { form } from 'neoform';\n\nimport MyInput from '../MyInput';\n\nconst MyForm = () =\u003e (\n  \u003cform\u003e\n    \u003cMyInput name=\"user.name\"/\u003e\n    \u003cMyInput name=\"user.status\"/\u003e\n    \u003cMyInput name=\"user.friends.0\"/\u003e\n  \u003c/form\u003e\n);\n\nexport default form(MyForm);\n```\n\n### App\n\nFinally, we assemble everything together:\n\n```js\nimport { setValue, getValue } from 'neoform-plain-object-helpers';\n\nimport MyForm from '../MyForm';\n\nclass App extends Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      data: props.data\n    };\n    this.onChange = this.onChange.bind(this);\n    this.onSubmit = this.onSubmit.bind(this);\n  }\n\n  onChange(name, value) {\n    this.setState((prevState) =\u003e setValue(prevState, name, value));\n  }\n\n  onSubmit() {\n    console.log('submit:', this.state.data);\n  }\n\n  render() {\n    \u003cMyForm\n      data={this.state.data}\n      getValue={getValue}\n      onChange={this.onChange}\n      onSubmit={this.onSubmit}\n    /\u003e\n  }\n}\n```\n\nWhat's going on here? As you may guessed, all fields in NeoForm are controlled. So, in order to update them, we need to update data state:\n\n#### `getValue`\n\nFirst, we need to specify `getValue` prop to tell NeoForm how exactly it should retrieve field value from data state. The reason to do that is because you might have a plain object data, Immutable or something else with a different \"interface\".\n\nInstead of writing your own `getValue` function, you can use one from  [neoform-plain-object-helpers](https://github.com/zero-plus-x/neoform/tree/master/packages/neoform-plain-object-helpers) or [neoform-immutable-helpers](https://github.com/zero-plus-x/neoform/tree/master/packages/neoform-immutable-helpers) package.\n\n`getValue` arguments:\n\n* `data` — form data state\n* `name` — field name\n\n#### `setValue`\n\nSecond, we have only one `onChange` handler for the entire form instead of multiple ones for each field. So, whenever some field requests a change, we need to update form data by updating the state so updated value is passed to that field with a new render.\n\n:information_source: Consider using [Recompose `pure()` HOC](https://github.com/acdlite/recompose/blob/master/docs/API.md#pure) or [`React.PureComponent`](https://facebook.github.io/react/docs/react-api.html#react.purecomponent) for fields to avoid unnecessary renders and get performance boost in some cases.\n\nInstead of writing your own handler, you can use `setValue` helper from  [neoform-plain-object-helpers](https://github.com/zero-plus-x/neoform/tree/master/packages/neoform-plain-object-helpers) or [neoform-immutable-helpers](https://github.com/zero-plus-x/neoform/tree/master/packages/neoform-immutable-helpers) package.\n\n`setValue` arguments:\n\n* `data` — form data state\n* `name` — field name\n* `value` — new field value\n\n```\n+--------------+\n|              |\n|              |\n|    +---------v---------+\n|    |                   |\n|    |    MyForm.data    |\n|    |                   |\n|    +---------+---------+\n|              |\n|       name   |\n|              |\n|    +---------v---------+\n|    |                   |\n|    |   MyInput.value   |\n|    |                   |\n|    +---------+---------+\n|              |\n|              |\n|    +---------v---------+\n|    |                   |\n|    | MyInput.onChange  |\n|    |                   |\n|    +---------+---------+\n|              |\n|       name   |  value\n|              |\n|    +---------v---------+\n|    |                   |\n|    |  MyForm.onChange  |\n|    |                   |\n|    +---------+---------+\n|              |\n|       name   |  value\n|              |\n+--------------+\n```\n\n### Validation\n\nValidation in NeoForm is always asynchronous.\n\n#### `fieldValidation`\n\n`fieldValidation` is another HOC:\n\n```js\nimport { field } from 'neoform';\nimport { fieldValidation } from 'neoform-validation';\n\nconst MyInput = ({\n  validate,\n  validationStatus,\n  validationMessage,\n  ...props\n}) =\u003e (\n  \u003cinput {...props} onBlur={validate} /\u003e\n  {\n    validationStatus === false \u0026\u0026 (\n      \u003cspan\u003e{validationMessage}\u003c/span\u003e\n    )\n  }\n)\n\nexport default field(fieldValidation(MyInput));\n```\n\nWhere the props are:\n\n* `validate` – validation action, can be called whenever you want (`onChange`, `onBlur`, etc)\n* `validationStatus` – `true` | `false` | `undefined` status of field validation\n* `validationMessage` – an optional message passed from validator\n\n#### `formValidation`\n\n```js\nimport { form } from 'neoform';\nimport { formValidation } from 'neoform-validation';\n\nimport MyInput from '../MyInput';\n\nconst MyForm = ({\n  /* data, */\n  validate,\n  validationStatus,\n  onInvalid,\n  onSubmit\n}) =\u003e (\n  \u003cform onSubmit={(e) =\u003e {\n    validate(onSubmit, onInvalid)\n    e.preventDefault();\n  }}\u003e\n    \u003cMyInput name=\"user.name\"/\u003e\n    \u003cMyInput name=\"user.status\"/\u003e\n    \u003cMyInput name=\"user.friends.0\"/\u003e\n  \u003c/form\u003e\n);\n\nexport default form(formValidation(MyForm));\n```\n\nWhere:\n\n* `validate` – entire form validation action: it will validate all fields and if they're valid it will invoke a first provided callback (`onSubmit` handler in most cases) or second callback (something like `onInvalid`) if they're invalid\n* `validationStatus` – `true` | `false` | `undefined` status of entire form validation\n\n#### Validators\n\n\"Validator\" is just a Promise. Rejected one is for `validationStatus: false` prop and resolved is for `validationStatus: true`. An optional argument passed to a rejected or fulfilled Promise becomes `validationMessage` prop.\n\n```js\nexport const requiredValidator = (value, type) =\u003e {\n  if (value === '') {\n    return Promise.reject('💩');\n  }\n\n  return Promise.resolve('🎉');\n};\n```\n\nWhere:\n\n* `value` – field value for validation\n* `type` – event type. Can be `submit`, `change`, `blur` or anything you will provide to field `validate` method\n\nIt's up to you how to manage multiple validators — with a simple `Promise.all()` or some complex asynchronous sequences — as long as validator returns a single Promise.\n\nTo use a validator you should just pass it in a `validator` prop to an individual field:\n\n```js\nimport { requiredValidator } from '../validators'\n\n// …\n\n\u003cform\u003e\n  \u003cMyInput name=\"user.name\" validator={requiredValidator} /\u003e\n  \u003cMyInput name=\"user.status\"/\u003e\n  \u003cMyInput name=\"user.friends.0\"/\u003e\n\u003c/form\u003e\n\n// …\n```\n\n:tv: [Check out live demo](https://www.webpackbin.com/bins/-KrbNqAfDYNwm07UmzTb).\n\n## FAQ\n\n\u003e But this is just like my entire form is a single component with a single `onChange`!\n\nRight.\n\n\u003e Does it affect performance because of re-rendering entire form on every field change?\n\nProbably in some cases it does. But as it was mentioned here before consider using [Recompose `pure()` HOC](https://github.com/acdlite/recompose/blob/master/docs/API.md#pure) or [`React.PureComponent`](https://facebook.github.io/react/docs/react-api.html#react.purecomponent) to avoid that.\n\n\u003e What about Redux?\n\nAbsolutely same approach: call an action on form `onChange` and then use plain/immutable helper to return updated data state from a reducer.\n\n\n## Status\n\nThis is a [monorepo](https://github.com/babel/babel/blob/master/doc/design/monorepo.md) composed of these packages:\n\n| package | version | description |\n| ------- | ------- | ----------- |\n| [neoform](packages/neoform/) | [![npm](https://img.shields.io/npm/v/neoform.svg?style=flat-square)](https://www.npmjs.com/package/neoform) | Core toolkit with `form` and `field` HOCs |\n| [neoform-validation](packages/neoform-validation/) | [![npm](https://img.shields.io/npm/v/neoform-validation.svg?style=flat-square)](https://www.npmjs.com/package/neoform-validation) | `formValidation` and `fieldValidation` HOCs |\n| [neoform-plain-object-helpers](packages/neoform-plain-object-helpers/) | [![npm](https://img.shields.io/npm/v/neoform-plain-object-helpers.svg?style=flat-square)](https://www.npmjs.com/package/neoform-plain-object-helpers) | `getValue` and `setValue` helpers for plain object state |\n| [neoform-immutable-helpers](packages/neoform-immutable-helpers/) | [![npm](https://img.shields.io/npm/v/neoform-immutable-helpers.svg?style=flat-square)](https://www.npmjs.com/package/neoform-immutable-helpers) | `getValue` and `setValue` helpers for [Immutable](https://github.com/facebook/immutable-js) state |\n\n## Development\n\n1. Create a new folder in `packages/`, let's say `neoform-foo`.\n2. See `package.json` in already existing packages and create new `neoform-foo/package.json`.\n3. Put source code in `neoform-foo/src/`, it will be transpiled and bundled into `neoform-foo/dist/`, `neoform-foo/lib/` and `neoform-foo/es/`.\n4. Put tests written with Jest in `neoform-foo/test/`.\n5. Put demo in `neoform-foo/demo/`, it will be rendered and wrapped with HMR.\n\nAvailable scripts using [Start](https://github.com/start-runner/start):\n\n```\nyarn start build \u003cpackage\u003e\nyarn start demo \u003cpackage\u003e\nyarn start test\nyarn start testWatch\nyarn start lint\n```\n\nAvailable demos:\n\n```\nyarn start demo neoform\nyarn start demo neoform-validation\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzero-plus-x%2Fneoform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzero-plus-x%2Fneoform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzero-plus-x%2Fneoform/lists"}