1.0.0 • Published 10 months ago

react-form-engine-lite v1.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

react-form-engine

React form engine, support any UI library. This package using hooks, note that your React version is above 16.8 :). Playground, the demo contains some examples of usage

Core characteristics

  • Schema based, easy to use
  • Not limited by any UI library, support custom form components
  • Custom layout, support very flexiable layout
  • Custom validation rules, support async function
  • Custom form linkage, support async function

Regist form components

Need to regist form components on init, widget means can be used to render field, wrapper means can be used to layout

import { Checkbox, Radio, Select, Switch, Input, InputNumber } from "antd";

registWidgets({
  Input: (props) => (
    <Input {...props} onChange={(e) => props.onChange(e.target.value)} />
  ),
  InputNumber,
  Switch,
  Select,
  Checkbox: (props) => (
    <Checkbox
      {...props}
      onChange={async (e) => {
        await props.onFocus();
        await props.onChange(e.target.checked);
        await props.onBlur();
      }}
    >
      {props.label}
    </Checkbox>
  ),
  CheckboxGroup: Checkbox.Group,
  RadioGroup: Radio.Group,
});

registWrappers({
  BlueBG: (props) => (
    <div {...props} style={{ ...props.style, backgroundColor: "cadetblue" }} />
  ),
});

Basic use

<FormEngine
  ref={form}
  value={data}
  title="Basic Use"
  fields={{
    name: {
      title: "Name",
      type: "Input",
      required: true,
    },
    age: {
      title: "Age",
      type: "InputNumber",
      required: true,
    },
    gender: {
      title: "Gender",
      type: "Select",
      required: true,
      options: [
        { value: "male", label: "Male" },
        { value: "female", label: "Female" },
      ],
    },
  }}
  layout={["name", "age", "gender"]}
  onChange={(data) => {}}
/>

Custom layout

Default every field occupy a row, but some scenarios need multiple fields in a row or need custom wrapper to wrap fields

<FormEngine
  ref={form}
  value={data}
  title="Custom Layout"
  fields={{
    name: {
      title: "Name",
      type: "Input",
      required: true,
    },
    age: {
      title: "Age",
      type: "InputNumber",
      required: true,
    },
    gender: {
      title: "Gender",
      type: "Select",
      required: true,
      options: [
        { value: "male", label: "Male" },
        { value: "female", label: "Female" },
      ],
    },
  }}
  layout={[
    {
      wrapper: "BlueBG",
      style: { padding: 20, width: "100%", marginBottom: 12 },
      children: [["name", "age"]],
    },
    "gender",
  ]}
  onChange={(data) => {}}
/>

Validation rule

Required property will check field value if empty, also we can define custom validation rule to a field

<FormEngine
  ref={form}
  value={data}
  title="Custom Validation Rule"
  fields={{
    name: {
      title: "Name",
      type: "Input",
      required: true,
    },
    age: {
      title: "Age",
      type: "InputNumber",
      required: true,
    },
    gender: {
      title: "Gender",
      type: "Select",
      required: true,
      props: {
        options: [
          { value: "male", label: "Male" },
          { value: "female", label: "Female" },
        ],
      },
    },
    vacationType: {
      title: "Vacation Type",
      type: "Select",
      required: true,
      validationRules: [
        [
          (value) =>
            data.gender === "male" ? value !== "breastfeeding" : true,
          "Male can't breastfeeding!!!",
        ],
      ],
      props: {
        options: [
          { value: "annual", label: "Annual" },
          { value: "sick", label: "Sick" },
          { value: "wedding", label: "Wedding" },
          { value: "breastfeeding", label: "Breastfeeding" },
        ],
      },
    },
  }}
  layout={[["name", "age", "gender"], "vacationType"]}
  onChange={setData}
/>

Field linkage

A field can has effect on other fields when changed, we can define this linkage

<FormEngine
  ref={form}
  value={data}
  title="Mutator"
  fields={{
    name: {
      title: "Name",
      type: "Input",
      required: true,
    },
    age: {
      title: "Age",
      type: "InputNumber",
      required: true,
    },
    gender: {
      title: "Gender",
      type: "Select",
      required: true,
      mutations: {
        vacationType: (vacationType, gender) => {
          // when change gender, we want to affect vacation type
          if (gender === "male") {
            const maleVacationTypes = [
              { value: "annual", label: "Annual" },
              { value: "sick", label: "Sick" },
              { value: "wedding", label: "Wedding" },
            ];

            if (vacationType === "breastfeeding") {
              return {
                value: undefined,
                options: maleVacationTypes,
              };
            }

            return {
              options: maleVacationTypes,
            };
          }

          return {
            options: [
              { value: "annual", label: "Annual" },
              { value: "sick", label: "Sick" },
              { value: "wedding", label: "Wedding" },
              { value: "breastfeeding", label: "Breastfeeding" },
            ],
          };
        },
      },
      options: [
        { value: "male", label: "Male" },
        { value: "female", label: "Female" },
      ],
    },
    vacationType: {
      title: "Vacation Type",
      type: "Select",
      required: true,
      options: [
        { value: "annual", label: "Annual" },
        { value: "sick", label: "Sick" },
        { value: "wedding", label: "Wedding" },
        { value: "breastfeeding", label: "Breastfeeding" },
      ],
    },
  }}
  layout={[["name", "age", "gender"], "vacationType"]}
  onChange={setData}
/>

Field

type Field = {
  type: string | Component<any>;
  props?: any;
  title?: ReactNode;
  hidden?: boolean;
  disabled?: boolean;
  required?: boolean;
  mutations?: RestrictKeyRecord<K, Mutation>;
  validationRules?: ValidationRule[];
  [metaName: string]: any;
};

props/mutations/validationRules can not be changed in a mutation, but props is reactive. Except properties defined above, others defined in Field will pass to component and can be changed by a mutation

Form component

type Component = ComponentType<{
  value: V;
  disabled: boolean;
  onBlur(): void;
  onFocus(): void;
  onChange(value: V): void;
}>;

These props need to pass to a custom component

Submit

When want to submit, we need to get if form is valid and form data

const form = useRef<Ref>(null);

<FormEngine ref={form} />;

<Button
  onClick={async () => {
    const { value, valid } = await form.current!.submit();
  }}
>
  Submit
</Button>;

Timing

Validation and mutation will execute on field onBlur and submit, so ensure onBlur is called in custom component and call formRef.submit before you post

1.0.0

10 months ago