{"id":19320247,"url":"https://github.com/jlyonsmith/react-form-binder","last_synced_at":"2026-06-22T10:32:05.791Z","repository":{"id":57333304,"uuid":"113245984","full_name":"jlyonsmith/react-form-binder","owner":"jlyonsmith","description":"A data binding controller for React form components","archived":false,"fork":false,"pushed_at":"2018-12-12T06:23:28.000Z","size":454,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-06-15T21:30:12.165Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/jlyonsmith.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}},"created_at":"2017-12-05T23:51:11.000Z","updated_at":"2022-12-06T12:52:24.000Z","dependencies_parsed_at":"2022-08-24T20:41:10.986Z","dependency_job_id":null,"html_url":"https://github.com/jlyonsmith/react-form-binder","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/jlyonsmith/react-form-binder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlyonsmith%2Freact-form-binder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlyonsmith%2Freact-form-binder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlyonsmith%2Freact-form-binder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlyonsmith%2Freact-form-binder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jlyonsmith","download_url":"https://codeload.github.com/jlyonsmith/react-form-binder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlyonsmith%2Freact-form-binder/sbom","scorecard":{"id":523739,"data":{"date":"2025-08-11","repo":{"name":"github.com/jlyonsmith/react-form-binder","commit":"77f19040a41efce13464a1f0622308527ea5b0d2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"66 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-c6rq-rjc2-86v2","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-q42p-pg8m-cqh6","Warn: Project is vulnerable to: GHSA-w457-6q6x-cgp9","Warn: Project is vulnerable to: GHSA-62gr-4qp9-h98f","Warn: Project is vulnerable to: GHSA-f52g-6jhx-586p","Warn: Project is vulnerable to: GHSA-2cf5-4w76-r9qv","Warn: Project is vulnerable to: GHSA-3cqr-58rm-57f8","Warn: Project is vulnerable to: GHSA-g9r4-xpmj-mj65","Warn: Project is vulnerable to: GHSA-q2c6-c6pm-g3gh","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-2pr6-76vf-7546","Warn: Project is vulnerable to: GHSA-8j8c-7jfh-h6hx","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-4xcv-9jjx-gfj3","Warn: Project is vulnerable to: GHSA-7wpw-2hjm-89gp","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-fhjf-83wg-r2j9","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp","Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr","Warn: Project is vulnerable to: GHSA-j44m-qm6p-hp7m","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T03:46:41.842Z","repository_id":57333304,"created_at":"2025-08-20T03:46:41.842Z","updated_at":"2025-08-20T03:46:41.842Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34645681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-10T01:27:59.641Z","updated_at":"2026-06-22T10:32:05.765Z","avatar_url":"https://github.com/jlyonsmith.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"A React data entry form binder. Automatically controls component state, validates and translates data to and from internal data objects. Works with both [React](https://reactjs.org/) and [React Native](https://facebook.github.io/react-native/).\r\n\r\nWhen building data entry forms with React there are a bunch of things that you will want to do in pretty much all cases:\r\n\r\n- Move data to and from an internal transmission or storage object, such as the payload of an API call or a database record.\r\n- Track whether the data has been modified and filter out only the modified properties\r\n- Validate that the React components on the form contain valid data\r\n- Set the enabled, read-only or visible state of components on the form based on the value of other components.\r\n- Transform data from and to the transmission/storage format, e.g. date/times from ISO 8601 to local time.\r\n- Be able to easily detect the validity and modification state of the entire form.\r\n\r\n## Glossary\r\n\r\n**Binding** - an object that defines how to move data back and forth from the internal data format to the React form components used to edit that data.\r\n\r\n**Binding Definition** - a set of properties that define the _binding_.\r\n\r\n**Binder** - an object that manages a collection of _bindings_.\r\n\r\n**Bound Component** - a React component that uses a _binder_ to control it's state. There are two types of bound components. Those that maintain a `value` as part of their `state`, e.g. text input, checkbox, dropdown, and those that maintain no `value`, e.g. 'OK' and 'Cancel' buttons. Non-`value` components can an still change their state based on the value of other bound components on the form.\r\n\r\n## Step-by-Step\r\n\r\nHere are the step-by-step instructions to use the library for a React application:\r\n\r\n1. Add the `bindings` definition object as a static property of the class, ensuring an `isValid` at a minimum for each binding. `isValid` can be any truthy value or a function returning a truthy value. Mark a binding as `noValue` if it does not map to a property in the data object.\r\n\r\n2. Construct the `FormBinder` object in the form component `constructor()` and assign it to either `this.binder` (or `this.state.binder` if the bindings will be changing)\r\n\r\n3. Set the `binder` property of each bound React component in the `form`. Typically, you'll want to remove any existing `onChange` handlers if converting from a non-bound component and ensure a `name` proprty. See below for how to create bound components.\r\n\r\n4. For HTML, in the `onSubmit` event handler method add `e.preventDefault()` (to avoid automatic form processing by HTML)\r\n\r\n5. Call `this.binder.getDeltaObject()` to get an object with just the modified values at any time.\r\n\r\n## Class: `Binding`\r\n\r\nBound forms work using an array of `Binding` objects that should be declared as a `static` property of the `form` component. Here is an example:\r\n\r\n```\r\nclass MyFormComponent extends React.Component {\r\n  ...\r\n  static bindings = {\r\n  \temail: {\r\n  \t  isValid: (b, v) =\u003e (regExpPattern.email.test(v)),\r\n  \t  isDisabled: (b) =\u003e (!!b._id)\r\n  \t},\r\n  \tchangeEmail: {\r\n  \t  nonValue: true,\r\n  \t  isDisabled: (b) =\u003e (!!b._id === false)\r\n  \t},\r\n  \t...\r\n  }\r\n\r\n  render() {\r\n    return (\r\n      \u003cform ...\u003e...\u003c/form\u003e\r\n    )\r\n  }\r\n}\r\n```\r\n\r\nEach property of the `bindings` object must correspond to the `name` property of a React component in the form.\r\n\r\nYou can use `.`'s in the name of the binding property, but you must quote the name. This defines a _path_ to a nested object within the internal data object (similar to paths in MongooseJS, for example).\r\n\r\nThe following are the various properties you can set for a binding definition.\r\n\r\n### Property: `noValue: bool`\r\n\r\nIf `true`, indicates the binding does not track a `value`. Only the `state.disabled`, `state.readOnly` and `state.visible` properties of the binding change in response to the value of other bindings. Default is `false`.\r\n\r\n### Property: `isDisabled: bool | (binder: FormBinder, value: any) =\u003e bool`\r\n\r\nA truthy value, or function returning a truthy value, that indicates determines `state.disabled` for the binding. It's up to the bound component to implement what disable looks like, e.g. graying out the component.\r\n\r\n### Property: `isReadOnly: bool | (binder: FormBinder, value: any) =\u003e bool`\r\n\r\nA truthy value, or function returning a truthy value, that determines `state.readOnly` for the binding. It's up to the bound component to implement what read-only looks \u0026 behaves like, e.g. prevent mouse clicks on the component.\r\n\r\n### Property: `alwaysGet: bool`\r\n\r\nSetting this on a binding means this value will always be returned in a call to `getDeltaObject` even if it has not changed.\r\n\r\n### Function: `isVisible: bool | (binder: FormBinder, value: any) =\u003e bool`\r\n\r\nA truthy value, or function returning a truthy value, that determines `state.visible` for the binding. It's up to the bound component to implement looks like, but it should generally correspond to being hidden or not.\r\n\r\n### Function: `isValid: bool | (binder: FormBinder, value: any) =\u003e bool`\r\n\r\nA truthy value, or function returning a truthy value, that determines `state.valid` for the binding. It's up to the bound component to implement looks like, but generally you want to display some kind of error message when this is `false`.\r\n\r\n### Function: `pre (binder: FormBinder, value: any) =\u003e any`\r\n\r\nA function used to pre-process the original object property for use as the binding value in the bound component. The binder looks through the binding names to find values in the original object. If a value is `undefined` the binder will use `\"\"` as the binding value by default. You can use this function to override that behavior.\r\n\r\nFor example, the original object may have a boolean property. If you want to use this value to set a checkbox INPUT control, you might set `pre: v =\u003e v || false` so that the checkbox has a valid value, even if the field is not initially set.\r\n\r\n### Function: `post: (binder: FormBinder, value: any) =\u003e any`\r\n\r\nA function use to post-process the binding `value` to a format used in the original object.\r\n\r\n## Class: `FormBinder(obj: object, bindings: Bindings, onAnyModified: () =\u003e ())`\r\n\r\nThe `FormBinder` class is responsible for holding a collection of bindings. Here's an example of how to construct the class:\r\n\r\n```\r\n\tthis.state = {\r\n\t  binder: new FormBinder(\r\n\t  \tthis.props.myObject,\r\n\t  \tMyForm.validations,\r\n\t  \tthis.props.onAnyModifiedChanged)\r\n\t}\r\n```\r\n\r\n### Constructor: `constructor(obj: Object, bindings: Bindings, onAnyModified: bool)`\r\n\r\nThe constructor takes the original object that is being modified by the form, the `Bindings` object, and a callback for when the overall form modification state changes.\r\n\r\nYou must pass the `FormBinder` instance to each bound component as a property `binder={this.state.binder}` so save it in `this` or `this.state`.\r\n\r\nAssigning the binder to the form state allows you to change it if the the object that the form displays changes. You can use the `componentWillReceiveProps` method to update the `FormBinder`, like so:\r\n\r\n```\r\n  componentWillReceiveProps(nextProps) {\r\n    if (nextProps.obj !== this.props.obj) {\r\n      this.setState({\r\n        binder: new FormBinder(nextProps.obj, MyForm.bindings, nextProps. nextProps.onAnyModifiedChanged)\r\n      })\r\n    }\r\n  }\r\n```\r\n\r\nThis would be useful if you allow a forms contents to be changed by some other onscreen element, such as a list box.\r\n\r\n### Property: `id: string`\r\n\r\nThis will be the value of any `_id` property on the original object. Typically, this value will be set for existing objects that were retrieved from an API, and not set if the object is being created for the first time. For this reason, it can be used in `binding` functions to determine if the object is being created for the first time or modified.\r\n\r\n### Property: `getOriginalObject: Object`\r\n\r\nA copy of the original object passed into the constructor.\r\n\r\n### Property: `getMetadata: Object`\r\n\r\nA copy of any metadata passed to the binder.\r\n\r\n### Method: `getBindingState(name: string): ValueState | NoValueState`\r\n\r\nCalled to get the state for a binding. Can be used by bound components, in `binding` callback functions and elsewhere.\r\n\r\n### Method: `getBindingValue(name: string): string`\r\n\r\nCalled to get just the `state.value` for a binding. For `noValue` bindings it will of course be `undefined`.\r\n\r\n### Method: `updateBindingValue(name: string, newValue: any): State`\r\n\r\nCalled to update the value of the binding by name. Typically used in bound components in response to user input. Will cause the state for all other bound components in the form to update their state also, if the change affect them.\r\n\r\n### Method: `getDeltaObject(): Object`\r\n\r\nReturns an object containing _just_ the modified binding values, the object `_id` and any bindings marked as `alwaysGet`. All the binding values are passed through any configured `post` function so they are in the original object data format. For example, you might call this when a `submit` button is clicked to get an object containing just the modified values to send to an API. You can also use a spread operator to blend this with the original values returned from `getOriginalObject`.\r\n\r\n## Class: `NoValueState`\r\n\r\nThe object returned from `noValue` bindings.\r\n\r\n### Property: `valid: bool`\r\n\r\nIs the data valid?\r\n\r\n### Property: `disable: bool`\r\n\r\nShould the bound component be disabled?\r\n\r\n### Property: `visible: bool`\r\n\r\nShould the bound component be visible?\r\n\r\n### Property: `readOnly: bool`\r\n\r\nIs the bound component read-only?\r\n\r\n## Class: `ValueState` extends `NoValueState`\r\n\r\nFor bindings with a value, `NoValueState` is extended with `value` and `modified` properties.\r\n\r\n### Property: `value: any`\r\n\r\nThe binding value\r\n\r\n### Property: `modified: bool`\r\n\r\nHas the bound component been modified from it's original value?\r\n\r\n## Building Bound components\r\n\r\n`FormBinder` enables you to easily build _bound components_ that are automatically set from an initial data object, and allow you to easily get an object back that reflects changes to that object. This gives you the most flexibility to create components that look good in your app, show errors the way you want to show them, etc..\r\n\r\nSee the [`example`](https://github.com/jlyonsmith/react-form-binder/tree/master/example) folder in GitHub for for a wide variety of other bound components, including:\r\n\r\n- A full featured credit card component which automatically formats and validates different types of credit card numbers\r\n- A masked input component which formats input according to a mask\r\n- A state dropdown component\r\n- A check box component\r\n- Valueless button components\r\n- A container component that can shows or hides a group of components based on other bound components.\r\n\r\nYou can build and run the example project with:\r\n\r\n```\r\ngit clone https://github.com/jlyonsmith/react-form-binder.git\r\ncd react-form-binder\r\nnpm install\r\nnpm run build\r\ncd example\r\nnpm install\r\nnpm start\r\n```\r\n\r\nOn MacOS with iTerm2 installed you can run everything super easily with:\r\n\r\n```\r\nnpm install -g snap-tool\r\ngit clone https://github.com/jlyonsmith/react-form-binder.git\r\ncd react-form-binder\r\nsnap install\r\nsnap start\r\n```\r\n\r\nLets break down the `BoundInput` component from the example, which happens to use SemanticUI. Of course you can use _any_ React UI framework you like. This is the most common bound component and is basically an `input` component with `type='text'`.\r\n\r\nFirst, we declare our React component, ensuring we get at least a `name`, `binder`, `message` and `label`:\r\n\r\n```\r\nexport class BoundInput extends React.Component {\r\n  static propTypes = {\r\n    name: PropTypes.string.isRequired,\r\n    binder: PropTypes.object.isRequired,\r\n    message: PropTypes.string.isRequired,\r\n    label: PropTypes.string.isRequired,\r\n    ...\r\n  }\r\n  ...\r\n}\r\n```\r\n\r\nIn the constructor we `bind()` some methods to `this` and set the initial `state`.\r\n\r\nWe also add an event listener for the `props.name` event. The `FormBinder` will fire an event with the name of the binding if some element of this bindings `state`, other than `value`, changes because of another binding value getting updated. The only way `value` should change is if this component updates it, or if the `binder` that this component is attached to changes.\r\n\r\nWe remove the listener in the unmount callback.\r\n\r\n```\r\n  constructor(props) {\r\n    super(props)\r\n    this.handleChange = this.handleChange.bind(this)\r\n    this.updateValue = this.updateValue.bind(this)\r\n    props.binder.addListener(props.name, this.updateValue)\r\n    this.state = props.binder.getBindingState(props.name)\r\n  }\r\n\r\n  updateValue(e) {\r\n    this.setState(e.state)\r\n  }\r\n\r\n  componentWillUnmount() {\r\n    this.props.binder.removeListener(this.props.name, this.updateValue)\r\n  }\r\n```\r\n\r\nWe implement the `handleChange` event which gets called every time the user types in the `input` element.\r\n\r\n```\r\n  handleChange(e, data) {\r\n    const { binder, name } = this.props\r\n    const state = binder.getBindingState(name)\r\n\r\n    if (!state.readOnly \u0026\u0026 !state.disabled) {\r\n      this.setState(binder.updateBindingValue(name, data.value))\r\n    }\r\n  }\r\n```\r\n\r\nNow we handle the `binder` property changing in the parent form:\r\n\r\n```\r\n  componentWillReceiveProps(nextProps) {\r\n    if (nextProps.binder !== this.props.binder) {\r\n      this.setState(nextProps.binder.getBindingState(nextProps.name))\r\n    }\r\n  }\r\n```\r\n\r\nFinally, the `render()` method to generate the DOM elements based on the binding `state`'s.\r\n\r\n```\r\n  render() {\r\n    return (\r\n      \u003cForm.Field error={!this.state.valid} width={this.props.width} disabled={this.state.disabled} className={this.props.className}\u003e\r\n        \u003clabel\u003e{this.props.label}\u003c/label\u003e\r\n        \u003cPopup content={this.props.message} position={this.props.position || 'bottom center'}\r\n          hoverable={true}\r\n          trigger={\r\n            \u003cInput {...this.otherProps} value={this.state.value} type='text'\r\n              name={this.props.name} onChange={this.handleChange} /\u003e\r\n          }\r\n        /\u003e\r\n      \u003c/Form.Field\u003e\r\n    )\r\n  }\r\n```\r\n\r\n## Release Notes\r\n\r\n### 3.0.0\r\n\r\nFollows closely on the release of **2.0.x** because I decided to remove all use of the term _field_ and instead double down on the term _binding_. This changes the API, which is breaking change and thus a major version number increment. In my opinion it makes the entire library more consistent and much easier to learn and understand. This release also passed the initial value to `pre` and uses the resulting value as the unmodified value against which to compare the binding value.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlyonsmith%2Freact-form-binder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjlyonsmith%2Freact-form-binder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlyonsmith%2Freact-form-binder/lists"}