{"id":20252434,"url":"https://github.com/ivandotv/dumba","last_synced_at":"2025-04-10T23:21:12.846Z","repository":{"id":36985895,"uuid":"358844368","full_name":"ivandotv/dumba","owner":"ivandotv","description":"Small library for handling forms with Mobx","archived":false,"fork":false,"pushed_at":"2025-04-07T00:30:02.000Z","size":1682,"stargazers_count":6,"open_issues_count":11,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-07T01:26:10.002Z","etag":null,"topics":["form","forms","mobx","mobx-form","mobx-react","mobx-react-lite","react"],"latest_commit_sha":null,"homepage":"https://dumba-demo.netlify.app/","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/ivandotv.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":"2021-04-17T10:04:34.000Z","updated_at":"2024-06-18T12:47:52.000Z","dependencies_parsed_at":"2023-09-28T21:30:46.347Z","dependency_job_id":"ed549b0b-dde9-4cb2-a368-d3742519f7e8","html_url":"https://github.com/ivandotv/dumba","commit_stats":{"total_commits":148,"total_committers":6,"mean_commits":"24.666666666666668","dds":"0.21621621621621623","last_synced_commit":"a1bc8f903743e3ce2861d320b1f41427aa42ecf3"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Fdumba","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Fdumba/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Fdumba/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Fdumba/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ivandotv","download_url":"https://codeload.github.com/ivandotv/dumba/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248313049,"owners_count":21082796,"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","forms","mobx","mobx-form","mobx-react","mobx-react-lite","react"],"created_at":"2024-11-14T10:16:42.067Z","updated_at":"2025-04-10T23:21:12.830Z","avatar_url":"https://github.com/ivandotv.png","language":"TypeScript","funding_links":[],"categories":["Awesome MobX"],"sub_categories":["Related projects and utilities"],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg width=\"256\" src=\".assets/dumba-small.png\" alt=\"library logo\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\nSmall library for handling form data with Mobx.js\n\u003c/p\u003e\n\n---\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ivandotv/dumba/actions/workflows/CI.yml\"\u003e\u003cimg src=\"https://github.com/ivandotv/dumba/actions/workflows/CI.yml/badge.svg\" alt=\"workflow status\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://app.codecov.io/gh/ivandotv/dumba\"\u003e\u003cimg src=\"https://img.shields.io/codecov/c/gh/ivandotv/dumba\" alt=\"code coverage\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/ivandotv/dumba/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/ivandotv/dumba\" alt=\"sofware licence\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003c!-- toc --\u003e\n\n- [Introduction](#introduction)\n- [Demo](#demo)\n- [Installation](#installation)\n- [Quick Usage](#quick-usage)\n- [Schema](#schema)\n  - [Schema Fields](#schema-fields)\n  - [Advanced field creation properties](#advanced-field-creation-properties)\n  - [Creating Form Field Validation](#creating-form-field-validation)\n- [Form Class](#form-class)\n- [API Documentation](#api-documentation)\n\n\u003c!-- tocstop --\u003e\n\n## Introduction\n\nDumba.js is a small library (2.4KB) for handling form data via [Mobx.js](https://github.com/mobxjs/mobx). If you use Mobx for your state management this library will help you to create fully reactive forms.\nIt supports asynchronous validation and a whole lot more.\nIt does not contain any validation rules though, so for actual field validation it is recommended to use tried and tested third party validation libraries like [Validator.js](https://github.com/validatorjs/validator.js)\n\n## Demo\n\nTake a look at this [form demo](https://dumba-demo.netlify.app/) that shows pretty much all the functionality of the library.\n\nOr look at the [source of the demo here on github](demo/README.md)\n\n## Installation\n\n```sh\nnpm install dumba\n```\n\n## Quick Usage\n\nTo use the library we need to create the `schema` object, which declares what fields exist and what validation tests to use for every field.\nIn the example below we are going to create a schema with only one field (`email`) and that field will have only one validation (there could be an array of validations), that will check if the field value is a valid email address.\n\n```ts\nimport { createField, createValidation } from 'dumba'\nimport isEmail from 'validator/lib/isEmail'\n\nconst schema = {\n  email: createField({\n    value: 'admin@example.com',\n    validations: createValidation(\n      (str: string) =\u003e isEmail(str),\n      'Not a valid email'\n    )\n  })\n}\n```\n\nAfter we create the schema, we use the `Form` class to create the class instance that will be connected to the actual HTML form. Form instance needs the schema object.\n\n```ts\nimport { Form } from 'dumba'\nimport { schema } from './schema'\nconst form = new Form(schema)\n```\n\nWhen the form instance is created, we use it to connect the fields to the actual HTML form. The form will have all the fields from the schema e.g.\n\n```ts\nconst form = new Form(schema)\nform.fields.email.value // field value\nform.fields.email.onChange //field change event handler to connect to html input\nform.fields.email.errors // array of validation errors (if any)\n\n// also there are properties and methods on the form instance itself\n\nform.isValid // if the form is valid\nform.isSubmitting // if form is in the process of submitting\nform.isValidating // if the form is in the process of validating (async validations)\nform.handleSubmit // submit the form\n```\n\nNow, let's connect the form to the Material UI TextField as an example\n\n```tsx\n//using material ui just as an example\nimport TextField from '@material-ui/core/TextField'\nimport { observer } from 'mobx-react-lite'\nimport { Form } from 'dumba'\nimport { schema } from './schema'\n\n//declare it outside of the component, usual Mobx rules apply.\nconst form = new Form(schema)\n\nconst FormDemo = observer(function FormDemo() {\n  //form submit function\n  const handleOnSubmit = () =\u003e\n    form.handleSubmit((form: Form) =\u003e Promise.resolve(true))\n\n  return (\n    \u003cform onSubmit={handleOnSubmit} autoComplete=\"off\" noValidate\u003e\n      \u003cTextField\n        type=\"text\"\n        id=\"email\"\n        name=\"email\"\n        label=\"Email\"\n        // disable while submit is in progress or async validation\n        disabled={form.isSubmitting || form.isValidating}\n        value={form.fields.email.value}\n        onChange={form.fields.email.onChange}\n        onBlur={form.fields.email.onChange}\n        // mark textfield as invalid (if there are any email errors)\n        error={!!form.fields.email.errors.length}\n        // display error text\n        helperText={\u003cDisplayErrors errors={form.fields.email.errors} /\u003e}\n        autoComplete=\"off\"\n      /\u003e\n      \u003cButton //form submit button\n        variant=\"contained\"\n        color=\"primary\"\n        type=\"submit\"\n        disabled={\n          // disable button while submit or async validation is in progress or form is invalid or not yet validated\n          form.isSubmitting ||\n          form.isValidating ||\n          !form.isValid ||\n          !form.isValidated\n        }\n      \u003e\n        Submit\n      \u003c/Button\u003e\n    \u003c/form\u003e\n  )\n})\n```\n\nAnd that's the gist of it. There is a lot more you can do with Dumba forms, make sure you read the rest of the documentation.\n\n## Schema\n\nSchema is the most important part of the library, all form logic lives in the schema.\nWith schema, we define constraints for the fields in the form.\n\n```ts\nconst schema = {\n  email: createField({\n    value: 'admin@example.com',\n    validations: createValidation(\n      (str: string) =\u003e isEmail(str),\n      'Not a valid email'\n    )\n  })\n}\n```\n\nWe just created a simple schema with only one field (`email`) that has an initial value of `admin@example.com` and, there is one validation for this field, it checks if what the user entered is a valid email address, if the address is not valid, this field will contain an error message `Not a valid email`.\n\nSchema can have any number of fields even nested fields.\n\n```ts\nconst schema = {\n  person: {\n    name: createField(),\n    lastName: createField(),\n    address: {\n      street: createField(),\n      zipCode: createField(),\n      city: createField(),\n      country: createField()\n    }\n  }\n}\n```\n\n### Schema Fields\n\nThe schema consists of the fields, every field element in the HTML form should have a corresponding field in the schema.\n\nSchema fields are created via `createField()` factory function.\nIn the example below we are going to create an `email` field.\n\n```ts\nconst schema = {\n  email: createField({\n    value: 'admin@example.com',\n    validations: [\n      createValidation((str: string) =\u003e isEmail(str), 'Not a valid email'),\n      createValidation(\n        (str: string) =\u003e isAlpha(str),\n        'Only letters are allowed'\n      )\n    ],\n    delay: 100,\n    bailEarly: true,\n    disabled: false\n  })\n}\n```\n\n- `value`- the initial value of the field\n- `validations` - single validation or array of `validations` for the field to be tested with. [Read more](#creating-field-validation)\n- `delay` - delay in running the field validations. This is very handy if you like to debounce user input and not run validations on every user keystroke.\n- `bailEarly` - mark the field as invalid as soon as the first validation for the field fails. This is only valid when there is more than one validation for the field.\n- `disabled` - determine if the field should be disabled. If the field is disabled, it will not be validated, and the form with a disabled field will always be valid.\n\n### Advanced field creation properties\n\n```ts\nconst schema = {\n  email: createField({\n    value: 'admin@example.com',\n    validations: [\n      createValidation((str: string) =\u003e isEmail(str), 'Not a valid email'),\n      createValidation(\n        (str: string) =\u003e isAlpha(str),\n        'Only letters are allowed'\n      )\n    ],\n    delay: 100,\n    bailEarly: true,\n    disabled: false\n  })\n}\n```\n\n- `parseValue` - `(evt: any, field: Field\u003cT\u003e) =\u003e any` function to **extract** the value from the field in the actual HTML form. By default this function just takes the value from `evt.currentTarget.value` and passes it for validation. For example, you can use it to limit the values that can be entered into the input field. In the example below we only allow the uppercase letter `A,B,c` to be entered.\n\n```js\nparseValue: (evt, field) =\u003e {\n  const newValue = evt.currentTarget.value\n\n  if (newValue.length === 0) {\n    return newValue\n  }\n  const currentValue = field.value\n\n  //todo - compile regex ahead of time\n  const regex = /^[ABC]+$/\n\n  const isOnlyABC = regex.test(newValue)\n  if (!isOnlyABC) {\n    return currentValue\n  }\n\n  return newValue\n}\n```\n\n- `dependsOn` - Fields in a schema can depend on other fields, which means that when the field in the `dependsOn` array (`location` in the example below) changes, validations for the `numPeople` field will be automatically triggered.\n\nIf the field to be validated depends on some other field in the form (one or more fields) every time the **dependency** field changes, all validations that **depend** on it will also be validated again.\nIn the example below imagine we want to validate the total number of people allowed at the party depending on if the party is on the beach or by the pool (`numPeople` depends on `location`).\n\n```ts\nconst schema: SchemaType = {\n  location: createField({\n    value: 'beach' // or pool\n  }),\n  numPeople: createField({\n    value: '',\n    dependsOn:['location'] // depends on the location field above\n    validations: [\n      createValidation(\n        (partyPeople: number, _field: Field, locationDependancy?: Field) =\u003e {\n          if (locationDependancy?.value === 'pool') {\n            if (partyPeople \u003e 20) {\n              return 'max pool party attendance is 40' // error message\n            }\n            return true //valid\n          }\n          if (locationDependancy?.value === 'beach') {\n            if (partyPeople \u003e 200) {\n              return 'max beach party attendance is 200' // error message\n            }\n            return true //valid\n          }\n        }\n      )\n    ]\n  })\n}\n```\n\nIf the field depends on other fields that are deeply nested in the object schema that you should use `dot (.)` as a field separator e.g. (`level1.level2.location`).\n\n- `shouldDisable` - `(value: string, field: Field, dependancy?: Field):boolean` if the function returns **true** schema field will be disabled, and no validations for the field will run and `disabled` property of the field will be `true`. This function is triggered only if `dependsOn` field is declared and not empty.\n  In the example below, if the user does not play any sports `favoriteSport` field (e.g. input type select) will be disabled\n\n```ts\nconst schema: SchemaType = {\n  playsSports: createField({\n    value: false // of true\n  }),\n  favoriteSport: createField({\n    value: '',\n    dependsOn: ['playsSports'],\n    shouldDisable: (_value, _field, playsSportsDependency) =\u003e {\n      return dependancy?.value === false\n    }\n  })\n}\n```\n\n### Creating Form Field Validation\n\nActual field validation tests are created via `createValidation` function. `createValidation` function accepts two arguments:\n\n- function to execute the validation. It should return true if field is valid, or if it **returns a string** it will mark the field as invalid, and the string will be used as the error, in this case, the second parameter (message) will be ignored. This is particularly useful when working with dependent fields.\n- message to be used as an error if validation fails\n\n```ts\nexport type ValidationFn = (\n  value: any, // field value\n  field: Field\u003cany\u003e, // reference to the Field instance\n  dependency?: Field\u003cany\u003e // depended Field instance that can also trigger the validation function\n) =\u003e boolean | Promise\u003cboolean\u003e\n```\n\nAlso, note that you can access the complete form via `field.form` or `dependency.form`\n\nexample function that checks if value is email:\n\n```ts\n// isEmail from validator library\nimport isEmail from 'validator/lib/isEmail'\ncreateValidation((str: string) =\u003e isEmail(str), 'Not a valid email')\n```\n\ncheck if the value is bigger than 3, if `false` return a `string` that will be used as an error message.\n\n```ts\ncreateValidation((num: number) =\u003e\n  Number.isInteger(num) \u0026\u0026 num \u003e 3\n    ? true\n    : 'Should be a number and bigger than 3'\n)\n```\n\nTake a look at the generated [API docs]() for the complete api\n\n## Form Class\n\n`Form` class is used to create **reactive** fields and event listeners based on the `schema` that is used.\n\n```js\nconst schema = {\n  email: createField({\n    value: 'admin@example.com',\n    validations: createValidation(\n      (str: string) =\u003e isEmail(str),\n      'Not a valid email'\n    )\n  })\n}\nconst form = new Form(schema)\n```\n\nOnce we have the form instance we use it to connect the form elements.\n\n```jsx\nimport { schema } from './my-schema'\n\nconst form = new Form(schema)\n\nconst FormDemo = observer(function FormDemo() {\n  //form submit function\n  const handleOnSubmit = () =\u003e\n    form.handleSubmit((form: Form) =\u003e Promise.resolve(true))\n\n  return (\n    \u003cform onSubmit={handleOnSubmit} autoComplete=\"off\" noValidate\u003e\n      \u003cinput\n        type=\"text\"\n        id=\"email\"\n        name=\"email\"\n        label=\"Email\"\n        disabled={form.isSubmitting || form.isValidating} // disable while submit is in progress or async validation\n        value={form.fields.email.value} //value of the field pulled from the schema\n        onChange={form.fields.email.onChange} //handle field change event\n        onBlur={form.fields.email.onChange} // handle blur event (same as onChange)\n      /\u003e\n      \u003cbutton //form submit button\n        type=\"submit\"\n        disabled={\n          // disable button while submit or async validation is in progress or form is invalid or not yet validated\n          form.isSubmitting ||\n          form.isValidating ||\n          !form.isValid ||\n          !form.isValidated\n        }\n      \u003e\n        Submit\n      \u003c/button\u003e\n    \u003c/form\u003e\n  )\n})\n```\n\nOnce the form is connected to the HTML elements, every time the input filed is changed the field will be re-validated.\nThere are other methods on the form class that you might find useful (`reset`, `isValid` ,`validated` etc..) please take a look at the generated API docs.\n\n## API Documentation\n\nAuto generated API docs are [here](docs/api/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivandotv%2Fdumba","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivandotv%2Fdumba","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivandotv%2Fdumba/lists"}