@maif/react-forms

Build react safe forms as fast as possible

Usage no npm install needed!

<script type="module">
  import maifReactForms from 'https://cdn.skypack.dev/@maif/react-forms';
</script>

README

@maif/react-forms

@maif/react-forms is a React form builder with batteries included. You just have to define a form flow and a JSON schema to render stylish, highly customizable and fully managed forms.

@maif/react-forms use yup and react-hook-form to build its forms

Playground

If you think that an example will explain it better, you can go roght now to our react-forms playground to discover a couple of examples with an inline editor.

Install

npm install @maif/react-forms

or

yarn add @maif/react-forms

Usage

You must define a form flow (this is just a javascript array which that represent the rendering order of the form fields) and a schema (that is define all field of your form with type or constraints for example)

example

import { Form, type, format, constraints } from 'react-form'

export const Example = () => { 
  const schema = {
    age: {
      type: type.number,
      label: 'age',
      placeholder: 'Your age',
      help: "Just your age",
      constraints: [
        constraints.required("your age is required"),
        constraints.min(18, 'You must be an adult'),
        constraints.integer('half years are not accepted'),
      ]
    }, 
    name: {
      type: type.string,
      disabled: true,
      label: 'name',
      placeholder: 'your name',
      constraints: [
        constraints.required('Your name is required')
      ],
    }
  }

  const flow = ['name', 'age']

  return (
    <Form
        schema={schema}
        flow={flow}
        onSubmit={item => console.log({ item })}
      />
    )
}

schema properties

  • type (required): the type of value. It provided by the imported object type. It could be just string like string, number, bool, object, date or file
  • format: Over the type you can display a special format for the field. It provided by the imported object format or just a string
    • select: display a react-select field with provided options
    • buttonsSelect: display a buttons group, drawn with the same options than the format select
    • code: if the type is string, display a code input (draw with react-ace) (add a props.setRef to get ace editor ref)
    • markdown: if the type is string, display a markdown input
    • text: if the type is string, display a textarea
    • email: if the type is string, display an email input
    • password: if the type is string, display a password input
    • hidden: if the type is string, add an hidden input in your form
    • form: if the type is object, display a form in your form draw with given schema and flow. The form drawn is collapsable, by default on the first input but with visibleOnCollapse you can choose which field will be visble or not.
  • array: boolean value to display multiple fields with "add" and "remove" buttons (false by default)
  • createOption: if select format is choosen, createOption property is to render a Creatable component
  • onCreateOption: if select format is choosen, onCreateOption property is a function called before new option creation
    {
      onCreateOption: (option) => myFunction(option)
    }
    
  • isMulti: if select format is choosen, isMulti property is to render a multiselect component
  • defaultKeyValue: if the format is setup to object, this default key/value is set for all added entries
  • visible: a boolean option to hide/display form field. It can be an object with ref property to get a value by reference an a key test as a function to test it. if there is no test key, it's base just on boolean value of the reference.
  • disabled: A boolean option to enable/disable form field
  • label: The label of form field (you can pass null to not render a label)
  • placeholder: the placeholder of form field
  • defaultValue: A default value to fill field by default
  • help: the text display hover a help button
  • className: you can customize classnames of field,
  • style: to styling a field, you can provide a json object with css
  • onChange: a callback function to the value change.
    ({rawValues, value, setValue}) => if (value.length) { setValue('entry', false) }
    
  • render: a function to completely custom the rendering of form field
    ({rawValues, value, onChange, error}) => <input type="text" className="is-invalid" value={value} onChange={e => onChange(e.target.value)} />
    
  • itemRender: a function to completely custom the rendering of form field items (Just in case of array is true)
    ({rawValues, value, onChange, error}) => <input type="text" className="is-invalid" value={value} onChange={e => onChange(e.target.value)} />
    
  • props: a json object merged with default props. For exemple, if format select is setup, you can add all props to customize react-select
  • options: An array of options for the select field (if format select is setup)
  • optionsFrom: A url to fetch array of options for the select field (if format select is setup)
  • transformer: A function to transform options to a valid format for react select, by default the code try to do it himself.
    {
      transformer: (value) => ({label: value.name, value: value.age})
    }
    
  • schema: a sub schema for the object value. this schema can contains constraints
  • conditionalSchema: an object to conditionnaly render an object value. Contains a ref to test as string and a switch as an array of object which contains default for a boolean options to set default value, condition as a function to run or just a value to test against ref value and schema and flow to draw the object value.
    {
      type: {
        type: type.string,
        format: format.select,
        defaultValue: "mammal",
        options: ["mammal", "fish"]
      },
      place: {
        type: type.object,
        format: format.form,
        conditionalSchema: {
          ref: 'type',
          switch: [
            {
              default: true,
              condition: ({rawData, ref}) => ref === "mammal",
              schema: {
                continent: {
                  type: type.string,
                },
                country: {
                  type: type.string,
                }
              }
            },
            {
              condition: "fish",
              schema: {
                ocean: {
                  type: type.string,
                },
                deepness: {
                  type: type.number,
                }
              }
            },
          ]
        }
      }
    }
    
  • constraints: a JSON array of constraints. see constraints section

Form properties

  • schema (required): the form schema
  • flow (optional): the flow. The order of the schema fields by key (Just a javascript array of strings). Fields can be group on collapsable item, in this case, you can add an object with label, flow and a boolean collapse property. Collapse item can draw inline form thanks to boolean inline property
  • onSubmit (required): a function run on the form submission (in case if the form is valid )
  • value (optional): default value
  • inputWrapper (optional): A custom component to wrap all input of the form
  • className (optional): A custom class name for the form
  • style (optional): A custom json style object for the form
  • footer (optional): a component to override the footer provided
{({ reset, valid }) => {
            return (
              <div className="d-flex justify-content-end">
                <button className="btn btn-primary m-3" onClick={reset}>reset</button>
                <button className="btn btn-success m-3" onClick={valid}>accept</button>
              </div>
            )
          }}
  • options (optional): an object to put some options for your form. see Form options for more informations.

Form options

  • httpClient: a function to override the basic fetch used by react-forms to get async values (for optionsFrom)
httpClient = {(url, method) => fetch(url, {
  method,
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
    'X-foo': 'bar'
  }
})} 
  • watch: a boolean to activate the automatic log of form value. A function can be set up to override the default logger.
  • autosubmit: a boolean to activate the automatic run of the onSubmit form properties on every change of values.
  • actions: an object to parameter footer buttons key. By default just submit button is displayed.
<Form 
  actions={
    reset: {
      display: false (value by default)
      label: 'reset' (value by default)
    },
    submit: {
      display: true (value by default)
      label: "save" (value by default)
    }
  }
/>

constraints

Possible constraints are provided by import constraints from @maif/react-form or can be wrotes on json

mixed

the following methods works for all type types of value.

constraints.required(message?:string)

Mark the value as required, which will not allow undefined or null as a value. The provided message (or default message) will be display under form field as an error if the value is missing.

constraints.required("this field is required")
{type: 'required', message: "this field is required"}

constraints.nullable()

Indicates the null is a valid value for the schema. Without nullable null will be treated as an error.

constraints.nullable()
{type: 'nullable'}

constraints.test(name: string, message?:string, test: (val: any) => boolean | Promise<boolean>)

Adds a test function to the validation chain. The test must provide a name, an error message and a validation function that takes in entry the current value and must return a boolean result. The test can return a promise that resolve a boolean result

constraints.test("name", 'not fifou', value => value === 'fifou')
{type: 'test', name: "name", message: 'not fifou', test: value => value === 'fifou'}

constraints.when(ref: string, test: (val: any) => boolean, then: any = [], otherwise: any = [])

Adjust field constraints based on a sibling fileds given by ref. You can provide a matcher function with test. Then provides the true constraints and otherwise the constraints for failure condition

constraints.when(
  'isBig', 
  (isBig) => !!isBig, 
  [constraint.count(5)],
  [constraint.count(1)],)
{
  type: 'when', 
  ref: 'isBig', 
  test: (isBig) => !!isBig,
  then: [constraint.count(5)],
  otherwise: [constraint.count(1)],
}

constraints.oneOf(arrayOfValues: any[], message?:string)

Whitelist a set of values and display an error under field if the provided value is not contains in this set.

constraints.oneOf(['foo', 'bar'], 'not foo or bar :(')
{type: 'oneOf', arrayOfValues: ['foo', 'bar'], message: 'not foo or bar :('}

string

the following methods works for string values. All methods for mixed are available.

constraints.url(message?:string)

Validate that the provided value matches an url pattern via regexp and display an error if the result id false.

 constraints.oneOf(['foo', 'bar'], 'not foo or bar :(')
{type: 'oneOf', arrayOfValues: ['foo', 'bar'], message: 'not foo or bar :('}

constraints.email(message?:string)

Validate that the provided value matches an email pattern via regexp and display an error if the result id false.

{type: 'email', message: 'not an email'}

constraints.uuid(message?:string)

Validate that the provided value matches an uuid pattern via regexp and display an error if the result id false.

{type: 'uuid', message: 'not an uuid'}

constraints.matches(regexp: RegExp, message?:string)

Test if value matche the provided regexp and display an error if the result id false.

constraints.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$^+=!*()@%&]).{8,1000}$/, errorMessage)
{type: 'matches', regexp: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$^+=!*()@%&]).{8,1000}$/, message: 'error'}

string or number

the following methods works for string or number values. All methods for mixed are available.

constraints.min(ref: number | Reference<number>, message: string)

Set the minimum value allowed and display an error if the provided value (for a number) or the length of the string is bigger.

{type: 'min', ref: 1, message: 'too small'}

constraints.max(ref: number | Reference<number>, message: string)

Set the maximun value allowed and display an error if provided value (for a number) or the length of the string is smaller.

{type: 'max', ref: 5, message: 'too high'}

number

The following methods works for number values. All methods for mixed are available.

constraints.positive(message?:string)

The value must be a positive number and display an error if it's not.

{type: 'positive', message: 'positive please'}

constraints.negative(message?:string)

The value must be a negative number and display an error if it's not.

{type: 'negative', message: 'negative please'}

constraints.integer(message?:string)

The value must be aa integer number and display an error if it's not.

{type: 'integer', message: 'integer please'}

constraints.lessThan(ref: number | Reference<number>, message: string)

Set the maximun value allowed and display an error if provided value (for a number) or the length of the string is smaller.

{type: 'lessThan', ref: 5, message: 'less please'}

constraints.moreThan(ref: number | Reference<number>, message: string)

Set the minimum value allowed and display an error if provided value (for a number) or the length of the string is bigger.

{type: 'moreThan', ref: 5, message: 'more please'}

array

the following methods works for basic types if the format is define to array. All methods for mixed are available.

constraints.length(value: number | Reference<number>, message?:string)

Set the length of the array and display an error if it's different.

{type: 'length', ref: 5, message: 'this array must have 5 elements'}

date

the following methods works for date values. All methods for mixed are available.

file

the following methods works for file values. All methods for mixed are available.

constraints.supportedFormat(arrayOfValues: string[], message?:string)

Whitelist a set of supported format for the provided file and display an error under field if the format is not contains in this set.

{type: 'supportedFormat', arrayOfValues: ['jpg', 'jpeg', 'png'], message: 'unsupported format'}

constraints.unsupportedFormat(arrayOfValues: string[], message?:string)

Whitelist a set of unsupported format for the provided file and display an error under field if the format is contains in this set.

{type: 'unsupportedFormat', arrayOfValues: ['jpg', 'jpeg', 'png'], message: 'unsupported format'}

constraints.maxSize(value: number, message?:string)

Set the maximun value allowed for the file size and display an error if the size of provided file is bigger.

{type: 'maxSize', message: 'file size too big'}