DEV Community

Ivan V.
Ivan V.

Posted on

Handling HTML Forms with Mobx and Dumba.js library

In this short post, I'm going to walk you through how I'm handling HTML forms with Mobx.js. I've developed my own little library (~2.4Kb) for handling HTML forms with Mobx, which makes the forms fully reactive. It supports fields that depend on other fields and asynchronous validation.

You can checkout the demo that shows pretty much all the functionality of the library or check out the source (library and demo)on github

Usage

To use the library we need to create the schema object, which declares what fields exist and what validation tests to use for every field.
In 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.

import { createField, createValidation } from 'dumba'
import isEmail from 'validator/lib/isEmail'

const schema = {
  email: createField({
    value: 'admin@example.com',
    validations: createValidation(
      (str: string) => isEmail(str),
      'Not a valid email'
    )
  })
}
Enter fullscreen mode Exit fullscreen mode

After 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 accepts the schema.

import { Form } from 'dumba'
import { schema } from './schema'
const form = new Form(schema)
Enter fullscreen mode Exit fullscreen mode

When 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.

const form = new Form(schema)
form.fields.email.value // field value
form.fields.email.onChange //field change event handler to connect to html input
form.fields.email.errors // array of validation errors (if any)

// also there are properties and methods on the form instance itself

form.isValid // boolean if the form is valid
form.isSubmitting// boolean if form is in the process of submitting
form.isValidating // boolean if the form is in the process of validating (async validations)
form.handleSubmit // submit the form function
Enter fullscreen mode Exit fullscreen mode

Now, let's connect the form to the Material UI TextField as an example

//using material ui just as an example
import TextField from '@material-ui/core/TextField'
import { observer } from 'mobx-react-lite'
import { Form } from 'dumba'
import { schema } from './schema'

//declare it outside of the component, usual Mobx rules apply.
const form = new Form(schema)

const FormDemo = observer(function FormDemo() {
  //form submit function
  const handleOnSubmit = () =>
    form.handleSubmit((form: Form) => Promise.resolve(true))

  return (
    <form onSubmit={handleOnSubmit} autoComplete="off" noValidate>
      <TextField
        type="text"
        id="email"
        name="email"
        label="Email"
        // disable while submit is in progress or async validation is running
        disabled={form.isSubmitting || form.isValidating} 
        value={form.fields.email.value}
        onChange={form.fields.email.onChange}
        onBlur={form.fields.email.onChange}
        // mark field as invalid (if there are any email errors)
        error={!!form.fields.email.errors.length}
        helperText={<DisplayErrors errors={form.fields.email.errors} />} // display error text
        autoComplete="off"
      />
      <Button //form submit button
        variant="contained"
        color="primary"
        type="submit"
        disabled={
          // disable button while submit or async validation is in progress or form is invalid or not yet validated
          form.isSubmitting ||
          form.isValidating ||
          !form.isValid ||
          !form.isValidated
        }
      >
        Submit
      </Button>
    </form>
  )
})
Enter fullscreen mode Exit fullscreen mode

And that's the gist of it. There is a lot more you can do with the library. For example, you can create fields that depend on other fields.

In the next example, we are going to create two schema fields location and numPeople. The latter will depend on the former which means that every time the location field value changes, a validation test for numPeople will also run.

const schema: SchemaType = {
  location: createField({
    value: 'beach' // or pool
  }),
  numPeople: createField({
    value: '',
    dependsOn:['location'] // depends on the location field above
    validations: [
      createValidation(
        (partyPeople: number, _field: Field, locationDependancy?: Field) => {
          if (locationDependancy?.value === 'pool') {
            if (partyPeople > 20) {
              return 'max pool party attendance is 40' // error message
            }
            return true //valid
          }
          if (locationDependancy?.value === 'beach') {
            if (partyPeople > 200) {
              return 'max beach party attendance is 200' // error message
            }
            return true //valid
          }
        }
      )
    ]
  })
}
Enter fullscreen mode Exit fullscreen mode

This little library has sped up my form handling workflow considerably, so I've decided to open source it.
I hope you find it useful.

Demo

Demo source

Library

Discussion (0)