Needle-Pinpointing Machine

    @kaliber/forms

    2.1.1 • Public • Published

    Forms

    A set of utilities to help you create forms in React.

    Motivation

    Creating ad-hoc forms using React hooks is quite easy, but there is one thing that is a bit hard to do: preventing to render the complete form on each keystroke.

    Another motivation is to make form handling within our applications more consistent.

    Installation

    yarn add @kaliber/forms
    

    Usage

    Please look at the example for more advanced use-cases.

    import { useForm, useFormField } from '@kaliber/forms'
    import { required, email } from '@kaliber/forms/validation'
    
    const validationErrors = {
      required: 'This field is required',
      email: 'This is not a valid email',
    }
    
    export function Basic() {
      const { form: { fields }, submit } = useForm({
        // provide initial values to populate the form with
        initialValues: {
          name: '',
          email: '',
        },
        // create the form structure, fields are essentially their validation functions
        fields: {
          name: required,
          email: [required, email],
        },
        // handle form submit
        onSubmit: handleSubmit,
      })
    
      return (
        <form onSubmit={submit}>
          <TextInput label='Name' field={fields.name} />
          <TextInput label='Email' field={fields.email} />
          <button type='submit'>Submit</button>
        </form>
      )
    
      function handleSubmit(snapshot) {
        // note that the snapshot can still be invalid
        console.log(snapshot)
      }
    }
    
    function TextInput({ label, field }) {
      const { name, state, eventHandlers } = useFormField(field)
      const { value = '', error, showError } = state
      return (
        <>
          <div>
            <label htmlFor={name}>{label}</label>
            <input id={name} type='text' {...{ name, value }} {...eventHandlers} />
          </div>
          {showError && <p>{validationErrors[error.id]}</p>}
        </>
      )
    }

    Reference

    See the example for use cases

    Hooks

    import { ... } from '@kaliber/forms'

    useForm

    Defines a form.

    const {
      form, // 'object' field containing the form
      submit, // handler that can be used to submit the form
      reset, // handler that can be used to reset the form
    } = useForm({
      fields, // form structure
      initialValues, // (optional) initial form values
      validate, // (optional) validation for the complete form
      onSubmit, // called when the form is submitted
      formId, // (optional) a custom id, can be useful when multiple forms are placed on the same page
    })
    Input
    fields An object with the shape: { [name: string]: FormField }.
    initialValues An object with the shape: { [name: keyof fields]: ValueFor<fields[name]> }
    validate One of Validate or Array<Validate>
    onSubmit A function that accepts a Snapshot
    FormField One of BasicField, ArrayField or ObjectField
    BasicField One of null, Validate or Array<Validate>
    ArrayField Created with array(fields) or array(validate, fields)
    ObjectField Created with object(fields) or object(validate, fields)
    Validate A function with the following shape: `(x, { form, parents }) => falsy
    ValueFor<BasicField> Value can be anything and depends on the value passed to onChange
    ValueFor<ArrayField> Value is an array with objects mirroring the fields of that array
    ValueFor<ObjectField> Value is an object mirroring the fields of that object
    Snapshot An object with the following shape: { invalid, value, error }
    Output
    form An object with the shape: { fields: { [name: string]: FormField } }. Note that form is an ObjectField
    submit A function that can be used as onSubmit handler
    reset A function that can be used to reset the form
    FormField One of BasicField, ArrayField or ObjectField
    BasicField Can be used with the useFormField hook
    ArrayField Can be used with the `useArrayFormField hook
    ObjectField Can be used with the `useObjectFormField hook

    useFormField

    Subscribes to state changes in the form field and provides the event handlers for form elements.

    const {
      name, // the fully qualified name of the form field
      state, // 'object' that contains the form field state
      eventHandlers, // 'object' that contains handlers which can be used by form elements
    } = useFormField(field)
    Output
    name The fully qualified name of the form field
    state An object
    - value The value of the field
    - error The validation error for the field
    - isSubmitted Indicates if the form was submitted
    - isVisited Indicates if the field has been visited (had focus)
    - hasFocus Indicates if the field currently has focus
    - invalid The same as !!error
    - showError Handy derived boolean to determine when to show an error
    eventHandlers An object
    - onBlur Handler for onBlur events
    - onFocus Handler for onFocus events
    - onChange handler for onChange events, accepts DOM event or value

    useNumberFormField

    Specialized version of useFormField that converts the value to a a number if possible.

    useBooleanFormField

    Specialized version of useFormField that uses the checked state of the event target as value.

    useArrayFormField

    Subscribes to state changes in the form field and provides helpers for the array field.

    const {
      name, // the fully qualified name of the form field
      state, // 'object' that contains the form field state
      helpers, // 'object' that contains handlers that can be used to manipulate the array field
    } = useArrayFormField(field)
    Output
    name The fully qualified name of the form field
    state An object
    - children The child fields (these are object type fields)
    - error The validation error for the field
    - isSubmitted Indicates if the form was submitted
    - invalid The same as !!error
    - showError Handy derived boolean to determine when to show an error
    helpers An object
    - add Handler to add a field, accepts an initialValue for the child field
    - remove Handler to remove a field, accepts the child field

    useObjectFormField

    Subscribes to state changes in the form field and provides the fields of the object.

    const {
      name, // the fully qualified name of the form field
      state, // 'object' that contains the form field state
      fields, // 'object' containing the fields
    } = useObjectFormField(field)
    Output
    name The fully qualified name of the form field
    state An object
    - error The validation error for the field
    - isSubmitted Indicates if the form was submitted
    - invalid The same as !!error
    - showError Handy derived boolean to determine when to show an error
    fields An object containing the fields

    useFormFieldSnapshot

    Subscribes to the state of a field (or form).

    const snapshot = useFormFieldSnapshot(form)
    Output
    snapshot An object
    - invalid Boolean indicating whether the field is invalid
    - error One of BasicError, ObjectError or ArrayError
    - value The value of the field
    BasicError The result of the validation function
    ObjectError An object with the shape: { self, children } where self is a BasicError and children an object with errors
    ArrayError An object with the shape: { self, children } where self is a BasicError and children an array with errors

    useFormFieldValue

    Subscribes to the value of a field (or form).

    const value = useFormFieldValue(field)
    Output
    value The value of the field.

    useFormFieldsValues

    Subscribes to the values of multiple fields (or forms).

    const values = useFormFieldsValues(fields)
    Output
    values An array with the values of the fields.

    Schema

    import { ... } from '@kaliber/forms'

    array

    Used to create an array form field.

    array(validationOrFields, fields)

    Has two signatures:

    array(validation, fields)
    array(fields)

    If you want to use heterogeneous arrays (different types) you can use a function instead of a fields object:

    array(initialValue => ({
      _type: required,
      ...(
        initialValue._type === 'content' ? { text: required } :
        initialValue._type === 'image' ? { image: required } :
        null
      )
    }))

    When rendering the array field you can render a different component based on the value of the field:

    const { state: { children }, helpers } = useArrayFormField(field)
    
    return (
      <>
        {children.map(field => {
          const { _type } = field.value.get()
          return (
            _type === 'content' ? <ContentForm key={field.name} {...{ field }} /> :
            _type === 'image' ? <ImageForm key={field.name} {...{ field }} /> :
            null
          )
        })}
        <button type='button' onClick={_ => helpers.add({ _type: 'content' })}>Add content</button>
        <button type='button' onClick={_ => helpers.add({ _type: 'image' })}>Add image</button>
      </>
    )

    object

    Used to create an object form field.

    object(validationOrFields, fields)

    Has two signatures:

    object(validation, fields)
    object(fields)

    Validation

    Validation functions have this shape: (value, { form, parents }) => falsy | { id, params }

    Input
    value The value of the form field
    form The value of the complete form
    parents An array with the values of the parents (when using object or array fields)
    Output
    id An identifier to translate the error into something for people
    params Parameters useful for constructing the validation error

    We provide a few commonly used validation functions.

    import { ... } from '@kaliber/forms/validation'
    required Reports when the value is 'falsy' and not 0 or false.
    optional An alias for null, it's there for consistency and readability.
    number Reports if the value can not be converted to a number.
    min and max Reports if the value is outside of the given value.
    minLength and maxLength Reports if the length of the value is outside of the given value.
    email Reports if the value does not vaguely look like an email address.
    error Utility to create an error object.

    Components

    When you want to conditionally render or set some props based on your current form state, you should avoid the use of useFormFieldSnapshot or useFormFieldValue in your form root, since that will re-render your entire form with each change. Rather you should create a specialised component and make the values available through a render prop.

    Because this is such a common usecase, we provide several of these components.

    FormFieldValue

    Props
    render A function with the following shape: `value => React.ReactNode
    field The field whose value is used as the value argument when calling render.
    Example
    <FormFieldValue field={fields.subscribeToNewsletter} render={value => (
      value && <TextInput label='Email' field={fields.email} />
    )}>

    FormFieldsValues

    Props
    render A function with the following shape: `values => React.ReactNode
    fields The fields whose values are used as the values argument when calling render.
    Example
    <FormFieldValue field={[fields.firstName, fields.lastName]} render={([firstName, lastName]) => (
      firstName && lastName && <Greeting>{firstName} {lastName}</Greeting>
    )}>

    FormFieldValid

    Props
    render A function with the following shape: `valid => React.ReactNode
    field The field whose validity state is used as the valid argument when calling render.
    Example
    <FormFieldValid field={form} render={valid => (
      <Button type="submit" disabled={!valid}>Verstuur</Button>
    )}>

    Missing feature?

    If the library has a constraint that prevents you from implementing a specific feature (outside of the library) start a conversation.

    If you think a feature should be added to the library because it solves a specific use case, discuss it to determine if the complexity of introducing it is worth it.

    Other libraries

    Existing libraries have one or more of the following problems:

    • Too big / too complex
    • Not following the React philosophy
    • Too much boilerplate for simple forms
    • Validation results are
      • hard to translate
      • difficult to use in combination with accessibility principles
    • Different guiding principles and trade-offs

    Guiding principles

    • Static form structure
    • No visual components
    • Clean DSL for creating the form structure
    • No async validation
    • Minimal / small
    • Translatable validation results
    • Conscious complexity / usefulness / commonality trade-offs

    Disclaimer

    This library is intended for internal use, we provide no support, use at your own risk. It does not import React, but expects it to be provided, which @kaliber/build can handle for you.

    This library is not transpiled.

    Install

    npm i @kaliber/forms

    DownloadsWeekly Downloads

    12

    Version

    2.1.1

    License

    MIT

    Unpacked Size

    39.7 kB

    Total Files

    17

    Last publish

    Collaborators

    • jerryduijm
    • hjsielcken
    • rmostertkaliber
    • kaliber-owner
    • daveykropf
    • eecolor
    • pkuepers
    • larsvankleef