0.0.9 • Published 4 years ago

simple-use-form v0.0.9

Weekly downloads
3
License
MIT
Repository
github
Last release
4 years ago

simple-use-form

Simple form state management for React forms

NPM JavaScript Style Guide

Feature requests and Bug fixes

I've had to create similar form management components on a number of personal and professional projects - I've open sourced the code in the hope that it will save you some time. Please feel free to open bugs and feature requests as issues in git

Install

yarn add simple-use-form

Concepts

This module aims to reduce the boilerplate associated with creating and maintaining the state for components that use forms. It does so by presenting an easy to understand hook based interface which accepts configuration information about a 'form' and returns up-to-date information about that form.

This module does not attempt to prescribe a particular style or appearance

Instead, the module provides a set of functions and values associated with the form, allowing you to attach it to any HTML and CSS implementation. One point to note is that the module is somewhat opinionated about which HTML tags and attributes should be used. This is done to encourage best practices when building cross browser, accessible applications. As such, you will find that the module works best when you use the idiomatic elements: input, select, form, etc. rather than styled divs.

Usage

At the core of this module is the idea that most of the information about how a form should behave can, and should, be stored as configuration. The configuration is made up of three objects, dubbed: FieldConfig, FormConfig, and FormOptions. The specifics of these props are described in detail below.

The following example shows the configuration for a simple login form which uses a very simple regexp to catch invalid email addresses.

import * as React from "react";

import { useForm } from "simple-use-form";

const fieldConfig = {
  email: {
    type: "text",
    validation: {
      minLength: 8,
      maxLength: 32,
      regexp: /^[a-zA-Z0-9.-_]+@[a-zA-Z0-9.]+$/,
    },
  },
  password: {
    type: "password",
    validation: {
      minLength: 8,
      maxLength: 32,
    },
  },
};

const Example = () => {
  const { fields, submitButton, form } = useForm(fieldConfig);

  return (
    <form {...form.props}>
      <div>
        <label>Email</label>
        <input {...fields.email.inputProps} />
      </div>

      <div>
        <label>Password</label>
        <input {...fields.password.inputProps} />
      </div>

      <button {...submitButton.props}>Submit</button>
    </form>
  );
};

Form Submission

You can specify a function to call when the form is submitted (when the user clicks the submit button or hits 'Enter' while the form is focussed). If the form contains any errors the submit handler will not be called. If you wish to make fields optional simple omit the validation configuration from them and they will always be valid.

Extending the example above, we can add a simple submit handler to push the user's information. The on submit handler is passed the state of the form when it is executed.

Note that the HTML markup for the form is unchanged - the behaviour is driven by the hook.

+ import logInUser from './user/login.ts'

const Example = () => {
-   const { fields, submitButton, form } = useForm(fieldConfig);
+   const { fields, submitButton, form } = useForm(fieldConfig, { onSubmit: (formState) => logInUser(formState.username.value, formState.password.value)});

  return (
    <form {...form.props}>
      <div>
        <label>Email</label>
        <input {...fields.email.inputProps} />
      </div>

      <div>
        <label>Password</label>
        <input {...fields.password.inputProps} />
      </div>

      <button {...submitButton.props}>Submit</button>
    </form>
  );
};

Form Validation

The form is automatically validated according to the FieldConfig provided. If you would like to perform an additional validation step to validate more complicated relationships which cannot easily be expressed on a per-field basis, you can do so by providing an onValidate function. This will be executed when the form is submitted, after the in-built validation and before the onSubmit handler.

Extending our example above, we can add a validation function to ensure that the two fields are not identical

import logInUser from './user/login.ts'

+ const validateForm = (formState: FormState) => {
+   return formState.email !== formState.password
+ }

const Example = () => {
- const { fields, submitButton, form } = useForm(fieldConfig, { onSubmit: (formState) => logInUser(formState.username.value, formState.password.value)});
+ const { fields, submitButton, form } = useForm(fieldConfig, { onValidate: validateForm, onSubmit: (formState) => logInUser(formState.username.value, formState.password.value)});
  return (
    <form {...form.props}>
      <div>
        <label>Email</label>
        <input {...fields.email.inputProps} />
      </div>

      <div>
        <label>Password</label>
        <input {...fields.password.inputProps} />
      </div>

      <button {...submitButton.props}>Submit</button>
    </form>
  );
};

Custom validation of a field

If the in-built validation functions are unable to capture your intended form validation behaviour you can create custom field validators. Extending once again the login example we can relax the validation behaviour for the username when it is equal to admin. Note that these are somewhat contrived examples and in a real app I probably wouldn't recommend this kind of split validation.

import * as React from "react";

import { useForm } from "simple-use-form";

const fieldConfig = {
  email: {
    type: "text",
    validation: {
      minLength: 5,
      maxLength: 32,
-     regexp: /^[a-zA-Z0-9.-_]+@[a-zA-Z0-9.]+$/,
+     custom: (value: string) => {
+       return /^[a-zA-Z0-9.-_]+@[a-zA-Z0-9.]+$/.test(value) || value === 'admin'
+     }
    },
  },
  password: {
    type: "password",
    validation: {
      minLength: 8,
      maxLength: 32,
    },
  },
};

API

const output = useForm(fieldConfig, formConfig, formOptions);

Field Config:

Consists of a set of key: value pairs where the key is the identifier for the form element. In the above examples we declare two form elements: email and password. Each field accepts a type value and an optional set of validators via the validation key. The options are detailed below:

Field TypeAvailable Validators
textrequired, minLength, maxLength, regexp, custom
passwordrequired, minLength, maxLength, regexp, custom
selectrequired
checkboxrequired

Validators

ValidatorTypeDescriptionExample
requiredbooleanEnsures the value is truthy{ required: true }
minLengthnumberEnsures the value is longer than the provided number{ minLength: 5 }
maxLengthnumberEnsures the value is shorter than the provided number{ maxLength: 10 }
regexpRegExpEnsures the value matches the provided regular expression{ regexp: /[a-z]+/ }
custom(val: string) => booleanEvaluates the function to determine whether value is valid{ custom: (value) => value.startsWith('A') }

Form Config:

ParameterTypeDescription
onSubmit(formState: FormState) => voidPass the current form state to the provided submit handler
onValidate(formState: FormState) => voidPass the current form state to the provided validation handler

The order of events is: in-built validation of each field => custom validation of each specified field => in-build validation of form => custom validation of form => submission of form

Form Options:

Additional configuration of the forms behaviour can be achieved by specifying form options. The following options are available

OptionTypeDefaultDescription
clearAfterSubmitbooleantrueClear the form state after submission
validationMode"onChange" | "onSubmit""onChange"Set to onSubmit to avoid validating the fields and form after every change event (useful for forms with complicated or asynchronous validation)

Form State

The form state is passed to custom form functions. The form state contains a key: value pair for each controlled field. Each field contains the following information:

{
  value: string | boolean, // Current field value
  isValid: boolean, // Whether field passed validation
  isFocussed: boolean, // Whether the field currently has focus
  isPristine: boolean, // False if the field has been changed
  isDirty: boolean, // True if the field has been changed
  isTouched: boolean, // True if the field has been blurred
  isUntouched: boolean, // False if the field has been blurred
}

Hook Output

The return value of the hook contains the following information:

{
  fields: {
    inputProps: Object, // Props to spread into the controlled input field
    value: boolean | string,
    isFocussed: boolean,
    isValid: boolean,
    isPristine: boolean,
    isDirty: boolean,
    isTouched: boolean,
    isUntouched: boolean,
  },
  submitButton: {
    isDisabled: boolean, // Whether the submit is currently disabled (maps to form.isValid)
    props: Object, // Props to spread into the form submit button
  },
  form: {
    isValid: boolean, // Whether the form is currently valid (maps to submitButton.isDisabled)
    reset: () => void,
    props: Object, // Props to spread into the form element
  },
}

License

MIT © jack-dunleavy


This hook is created using create-react-hook.

0.0.9

4 years ago

0.0.8

4 years ago

0.0.7

4 years ago

0.0.5

4 years ago

0.0.6

4 years ago

0.0.3

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago