0.8.8 • Published 2 years ago

me-form-backend v0.8.8

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

me-form-backend

compose awesome forms

Get Started

yarn add me-form-backend
// or
npm install me-form-backend

Basic Example

import Form, { useFormItem, FormItem, ArrayField } from 'me-form-backend'

const Input = props => {
  const { value, onChange, rest } = useFormItem(props)
  return <input {...rest} value={value} onChange={onChange} />
}

const App = () => (
  <Form
    initValue={{ f1: { a: 'a' }, f2: [{ text: 'foo' }, { text: 'bar' }] }}
    onSubmit={formData => {}}
  >
    <FormItem name="f1">
      <Input name="a" />
    </FormItem>
    <ArrayField name="f2">{() => <Input name="text" />}</ArrayField>
    <button>submit</button>
  </Form>
)

Form

Form is the root of your form context, it provides form data to FormItem, useFormItem & ArrayField.

Props:

PropDescription
childrenReact elements rendered in Form or render prop: ({submit, value, error, validate}) => {}
validatora callback called before onSubmit, return null for no error or an error descriptor
onSubmitcallback called when form submit and all validators pass, receive the form data as argument
valuepass form data from parent component, Form with value and changed props is in controlled mode
onChangecallback used in combination with "value" and is called when form data changes
initValueinitial value for uncontrolled mode
defaultValuegets merged into initValue or value on some undefined field, usefull for some field is missing in value
formTagbool type, whether render an html form tag, default false
errorMessagesan object, which keys are validator rules and values are corresponding error messages

Controlled vs Uncontrolled

Form, much like React controls, provides two modes: controlled and uncontroled:

Controlled, pass value & onChange to Form component, onChange will be called whenever form data changes.

<Form value={...} onChange={...}>...</Form>

Uncontrolled, pass initValue to Form component, you can get form data in submit callback.

<Form initValue={...}>...</Form>

Why defaultValue can't be set on fields

Because currently in React there's not a good way that a parent component can read props from descendant components, so in the first render the defaultValue prop of a field can't be available from value of ancestors. This can be confusing and this is against the data flow model of React.

useFormItem

useFormItem is a React custom hook. It connects a form control component with Form context

Input Options

Usualy we just pass all props to useFormItem hook as input is just fine, useFormItem forwards all unknown props, but you may want to control it, all used props listed below:

propertydescription
namestring for field name
validatorcallback whenever field value changes, return null as no error, or an error descriptor, besides field value, if second argument is true, it means the validator is called by submit
errorMessagesan object, which keys are validator rules and values are corresponding error messages
requiedbool, shorthand for required validator
minLengthnumber, shorthand for min length validator
labelReactNode, it will be passed to dynamic error message function
parsea function called when field value changes, receive changed value, can be used like trim input or parse date from string
formata function called when pass value to control component, receive current value, can be used when format date to string
interceptorDeprecated use parse instead. A function called when field value changes, receive changed value, can be used like trim input
didUpdatea callback that called when a field is confirmed to change, receive changed value and a patch fucntion

didUpdate v.s. interceptor

  • interceptor is called right before a field value is changed, and it only provide the changed value. It's easy to use, but only can change the changed value
  • didUpdate is called before the changed value is committed, aka set the Form internal state in uncontrolled mode or call onChange prop of Form in controlled mode. It is provide the changed value and a patch function for the Form value, you can use the patch function change or remove any other fields of the Form, it's useful when you want to accomplish associated field changing
<Form initValue={{a: "", b: ""}}>
<Input
  ...
  name="a"
  didUpdate={(changedValue, patch) => {
    if (changedValue) {
      patch({b: "b also changed", c: "c added"}) // when a changing to true, set b and add c
    } else {
      patch({b: ""}, "c") // when a changing to false, reset b, and remove c
    }
  }}
/>
</Form>

Output Options

propertydescription
valuefield value
onChangeonChange callback
errorerror descriptor of current field, null if no error
resetErrora callback to clear current error
labelidentical label passed to the hook
ida dot concated string of all ancestors' name and the current field name, you can pass it to as for attribute and a control id attribute to associate a and a control

FormItem & ArrayField

FormItem and ArrayField are designed to handle middle layer fields, like object or array.

FormItem

FormItem can be used to describe a object field, which transform context value to a sub-field.

<Form initValue={{ obj: { a: 'a', b: 'b' } }}>
  <FormItem name="obj">
    <Input name="a" />
    <Input name="b" />
  </FormItem>
</Form>

It receives all options of useFormItem as props.

ArrayField

ArrayField is a shorthand for iterate over an array field, which uses children as render prop for each item's render.

In the childern render, context is transformed to each item.

<Form initValue={{ arr1: ['a', 'b'], arr2: [{text: 'foo'}, {text: 'bar'}}] }}>
  <div>arr1</div>
  <ArrayField name="arr1">
    {() => <Input />}  // you can get item value in arr1 without name prop, because context has been transformed to each item
  </ArrayField>
  <div>arr2</div>
  <ArrayField name="arr2">
    {() => <Input name="text"/>} // you can get item properties of arr2 by name prop, because context has been transformed to each item
  </ArrayField>
</Form>

/*
render as
<form>
  <div>arr1</div>
  <input value="a"/>
  <input value="b"/>
  <div>arr2</div>
  <input name="text" value="foo"/>
  <input name="text" value="bar"/>
</form>
*/

Validation & Error

Form, FormItem and useFormItem all receive a validator for form validation.

Validator should receive form data or the field value it has been defined, returns null if no error or error descriptor if any error exists.

ErrorDescriptor

  • rule defines the type of the error, it's used to find error message or to decide whether the error is ralated a field.
  • message can be a plain string, if not provided, error message will be computed from errorMessages.
  • labels is an array of field labels passed to message format funcion above metioned, if not provided, field label prop will be used.
interface ErrorDescriptor {
  rule: string
  message?: string
  labels?: ReactNode[] // string or react element
}

;<Form
  initValue={{ a: 'a', b: 'b' }}
  validator={({ a, b }) =>
    a !== b ? { rule: 'same value', message: 'a should be same as b' } : null
  }
>
  <Input name="a" />
  <Input name="b" />
</Form>

ErrorMessages

ErrorMessages is an object, which keys are validator rules and values are corresponding error messages.

Value can be a plain string or a function which receive field labels and return a string.

ErrorMessages can be config at multiple level: Form, FormItem or Control, it is merged top down, a later one overrides smae rule.

Trigger timing

Form validation is different from fields validation at trigger timing.

Form validation is triggered when form is to be submitted, and it triggers all fields' validators first, and at last is the validator defined at Form.

Other validators, defined at FormItem or useFormItem, is triggered when the field value changes.

Recipes

parse & format

const DateInput = () => (
  <Input
    parse={date => date.format('YYYY-MM-DD')}
    format={s => moment(s, 'YYYY-MM-DD')}
  />
)
0.8.8

2 years ago

0.8.7

2 years ago

0.8.6

4 years ago

0.8.5

4 years ago

0.8.4

4 years ago

0.8.3

4 years ago

0.8.2

4 years ago

0.8.1

4 years ago

0.8.0

4 years ago

0.7.11

4 years ago

0.7.10

5 years ago

0.7.9

5 years ago

0.7.8

5 years ago

0.7.7

5 years ago

0.7.6

5 years ago

0.7.5

5 years ago

0.7.4

5 years ago

0.7.3

5 years ago

0.7.2

5 years ago

0.7.1

5 years ago

0.7.0

5 years ago

0.6.1

5 years ago

0.6.0

5 years ago

0.5.8

5 years ago

0.5.7

5 years ago

0.5.6

5 years ago

0.5.5

5 years ago

0.5.4

5 years ago

0.5.3

5 years ago

0.5.2

5 years ago

0.5.1

5 years ago

0.5.0

5 years ago

0.4.4

5 years ago

0.4.3

5 years ago

0.4.2

5 years ago

0.4.1

5 years ago

0.4.0

5 years ago

0.3.2

5 years ago

0.3.1

5 years ago

0.3.0

5 years ago

0.2.0

5 years ago

0.1.7

5 years ago

0.1.6

5 years ago

0.1.5

5 years ago

0.1.4

5 years ago

0.1.3

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago