@speedlo/xform
TypeScript icon, indicating that this package has built-in type declarations

1.1.3 • Public • Published

XForm

Heavily inspired by Formik, but using MobX for performance and less buggy behavior.

XForm is centered around fields which are used to control all form features.

How to setup a form

To setup a form the useXForm hook is used which provides xform bag of state, api and handlers.

const MyForm = () => {
  const xform = useXForm({
    initialValues: {
      name: '',
    },
    onSubmit(values) {
      // submit values
    },
  })
  return <XFormRender xform={xform}>{children}</XFormRender>
}

Alternatively for simple forms you can do following which joins xform and XFormRender together.

const MyForm = () => {
  return <XForm initialValues={{ name: '' }} onSubmit={values => /* ... */}>
    {children}
  </XForm>
}

The XFormRender is tiny wrapper around form element that automatically listens to submit event. There is also XFormProvider to keep the xform around the tree. Any form fields has to be rendered within its tree.

The initialValues are required, but can be empty object. Field value would be undefined by default so bear that in mind. Values can be nested, there are no restrictions. Arrays are supported as well.

The second required prop is onSubmit which is executed after successful validation and provides you with values that can be sent out to the server or whatnot.

In case of a failed validation, an optional onSubmitFailed is called which can be used to collect state.errors and display those to user. Note it has nothing to do with result of an actual submit which is completely in your control.

Fields usage

To declare a field you have to call useXField which accepts name of field. In case of nested field either use the.deep[1].field notation or directly pass an array like ['the', 1, 'deep', 'field'].

import { useObserver } from 'mobx-react-lite'

const EnabledCheckbox = () => {
  const field = useXField('enabled')
  return useObserver(() => (
    <input
      type="checkbox"
      id={field.name}
      name={field.name}
      checked={field.value === true}
      onChange={() => field.setValue(!field.value)}
      onBlur={field.setTouched}
    />
  ))
}

Note the useObserver is essential otherwise field won't be reactive. You can also use observer HOC or Observer component.

Touching fields

The concept of touched state represents the scenario where user has somehow interacted with the field. Tracking touched state can be used to show the field state differently, eg. failed validation which would be annoying to display for all fields right away. Simply call field.setTouched whenever you feel like it's time. Method accepts boolean value in case you want to toggle touched state off again.

When the form is submitted, the field.touched is automatically turned true as submitting is considered as a final user action like he has touched everything. However, the actual value of touched state is still available in field.touchedPure.

Input field

Since the basic input is so common, the hook useXFieldForInput is exported and can be used like following. It handles onChange, onBlur and value props.

const FormInput = () => {
  const [field, getInputProps] = useXFieldForInput < string > fieldName
  return useObserver(() => (
    <input
      {...getInputProps()}
      invalid={field.touched && Boolean(field.error)}
    />
  ))
}

Validation

It's recommended to use form level validation for most of the forms. However, it's possible to declare a field with its own validation function to have a reusable component that does not rely on schema validation.

For a field to be considered for a validation it has to be registered. It is done automatically as a part of useXField hook, but each field can be registered only once. For the purposes of accessing other fields state, the useXFieldState can be used.

Generally, the validation runs in low priority mode and is debounced by lowPriorityDebounceIntervalMs (250ms by default). To force immediate validation of a whole form call api.validateForm can be called. The same function is used right before form is submitted to ensure everything has the right value.

By default validation is scheduled after field value change and when the field becomes touched (eg. onBlur). In case you want to revalidate on each "blur", it's necessary to field.setTouched(false). (eg. onFocus or onChange).

During the validation the state.isValidating flag is toggled so it can be reflected in UI, especially for async validations.

Form validation

XForm validation is build on top of Yup library for declaring a validation schema. The library comes with XForm and you can use createValidationSchema exported function to declare schema in the easy way.

const schema = createValidationSchema(yup => ({
  name: yup
    .string()
    .required()
    .min(10),
  age: yup.number().moreThan(18),
}))

It's recommended for schema to cover all form values. In case you omit some, the warning will be shown in development during validation to prevent typo errors. If you wish to supress these warnings, there is a ignoreUnknownFieldsInValidation configuration option, but consider yourself warned.

Field validation

The field level validation is a simple function which is expected to return either string with error or nothing. Function can be asynchronous and return Promise with the same. Don't reject the Promise unless you really mean it.

const validate = value => {
  if (!rxDollar.test(value)) {
    return "It's not a dollar!"
  }
}

const DollarInput = () => {
  const field = useXField('price', { validate })
}

Form submission

Is done by either calling api.submitForm imperatively, or by attaching handlers.handleSubmit to onSubmit event of the form element. This is done automatically in XFormRender for you.

The state.isSubmitting is used to track the process of submission.

Loading data asynchronously

It's possible to call api.mergeValues later when initial values are loaded. In this scenario it's recommended to set initialValidationEnabled to false and when data are loaded call api.enableValidation to avoid validating against empty initial values.

Tracking changes

The state.isDirty compares current state.values (deeply) against state.pristineValues which are set to initialValues by default. It's possible to change state.pristineValues to mark a point from which should be changes tracked (eg. after loading data)

Readme

Keywords

none

Package Sidebar

Install

npm i @speedlo/xform

Weekly Downloads

1

Version

1.1.3

License

none

Unpacked Size

191 kB

Total Files

43

Last publish

Collaborators

  • admin_speedlo
  • hanapp