React backend projects are mostly forms processing, such as the following 4 common 1* N layout (if manually coded, a large number of Row,Col, Form.Item nesting, arrangement, if combined with linkage, the code will be bloated and difficult to maintain)

  1. A line of a column

  1. A line of two columns

  1. A line of three columns

  1. A line of four columns

For this column of form development, it can be generated based on configuration, we can define an array, each item of the array is a form item, for row by column, we can render a component row by row from top to bottom, pseudo-code is as follows

       return arr.map((item, idx) = > itemRender(item, idx, 24))
Copy the code

For a row of N columns (n=2/3/4, refer to antD grid layout, a row of up to 4 form items ant. Design/Components /…) Based on the 24-grid system, we can calculate the number of grids occupied by each component 24/ N. Based on this, we can dynamically create a Grid and render a group of rows from top to bottom to achieve a multi-column layout. The pseudo-code is as follows

        const len = group.length;
        const span = 24 / len;

        return (
          <Row key={idx}>
            {arr.map((item, subIndex) => itemRender(item, subIndex, span))}
          </Row>
        );
Copy the code

The above itemRender is used for the Render Form component. Antd4 Form items are usually written like this: A form. Item wraps a Form control, as shown below

    <Form.Item
        label="Username"
        name="username"
        rules={[{ required: true.message: 'Please input your username! ' }]}
      >
        <Input />
      </Form.Item>
Copy the code

Render what component, such as Input/Select/, etc., and configure the component props 2.form. Item configuration. We can design generic JS objects to represent this information

{ type? : React.ComponentType | string;// Component type, such as Input, etcname? : string;/ / Form. The name of the Itemlabel? : string;/ / Form. The Item's labelelProps? : Record<string, unknown>;// The component props configuration, for example, if type is Input, elProps is configured to InputitemProps? : Record<string, unknown>;// props for form. Item (name,lable,rulesrules? : Rule[];/ / Form. The rules of the Item
};
Copy the code

Based on the js object configuration information above, we can implement itemRender to dynamically create components and layouts

const itemRender = (item: Item, key: number | string, span = 24) = > {
  if (typeofitem ! = ='object'| |! item)return null;

  const{ type, name, rules, label, elProps = {}, itemProps = {}, render, ... props } = item;return (
    <Col span={span} key={key}>
     <Form.Item name={name} label={label} rules={rules} {. itemProps} >{React.createElement(type, { ... props, ... elProps } as React.Attributes)}</Form.Item>
    </Col>
  );
};
Copy the code

To facilitate form linkage and support render for any component (not just the form), we can extend JS with the Render and getJSON methods (better called getConfigJs).

{ type? : React.ComponentType | string;// Component type, such as Input, etcname? : string;/ / Form. The name of the Itemlabel? : string;/ / Form. The Item's labelrender? :() = > React.ReactNode; // Customize rendergetJSON? :() = > Item | null; // Dynamically returns the Item configurationelProps? : Record<string, unknown>;// The component props configuration, for example, if type is Input, elProps is configured to InputitemProps? : Record<string, unknown>;// props for form. Item (name,lable,rulesrules? : Rule[];/ / Form. The rules of the Item
};
Copy the code

Since then, a generic ANTD form-render has been written, which can be found at github.com/leonwgc/ant…

The installation

Install using NPM/YARN:

$ npm install --save antd-form-render
$ yarn add antd-form-render
Copy the code

function

  • Configure a one-dimensional array to implement 1 row and N columns (automatic layout, top-down layout, left-to-right layout, refer to automatic car driving) n can be 1/2/3/4, default 1
  • Configure a two-dimensional array to achieve 1 row n columns (manual layout, each row shows several columns according to the length of the array, refer to the car manual drive)
  • Form linkage is naturally supported, see example 1 below for the gender selection code
  • Support for custom render, when antD components do not meet the requirements, can be customized to return any React node
  • Support dynamic return of JS objects, refer to the following example 1 gender selection code, male dynamically generated input box, female drop-down box
  • Data collection, name as key, corresponding form control value is value

Implement 1 row 1 column

import React, { useState } from 'react';
import FormRender from 'antd-form-render';
import { Form, Button, Space, Input, Radio, Select } from 'antd';

export default function App() {
  const [data, setData] = useState({});

  / / define the form
  const [form] = Form.useForm();

  // A one-dimensional array defines layout, which puts a form control one line down from the top
  const layout = [
    {
      type: Input,
      label: 'Mobile phone Number'.placeholder: 'Please enter'.name: 'tel'.// Configure the Input, elProps for the component specified by type
      elProps: {
        maxLength: 11,},// Configure form.item
      itemProps: {
        rules: [{required: true.message: 'Please enter' },
          { pattern: /^1\d{10}$/, message: 'Mobile phone number must be 11 digits'},],},}, {type: Input.Password,
      label: 'password'.placeholder: 'Please enter'.name: 'pwd'.itemProps: {
        rules: [{ required: true.message: 'Please enter'}],}}, {type: Input.Password,
      label: 'Confirm password'.placeholder: 'Please enter'.name: 'confirmPwd'.itemProps: {
        rules: [{required: true.message: 'Please enter' },
          ({ getFieldValue }) = > ({
            validator(_, value) {
              if(! value || getFieldValue('pwd') === value) {
                return Promise.resolve();
              }
              return Promise.reject(new Error('Two different passwords')); },},],},}, {type: Radio.Group,
      label: 'gender'.name: 'gender'.elProps: {
        options: [{label: 'male'.value: 'male' },
          { label: 'woman'.value: 'woman'},],},}, {// Dynamically return object based on conditions
      getJSON() {
        return data.gender === 'male'
          ? {
              type: Input,
              label: 'Hobbies and Interests (Male)'.placeholder: 'Please enter your interests and hobbies'.name: 'hobby'.itemProps: {
                rules: [{ required: true.message: 'Please enter your interests and hobbies' }],
              },
            }
          : data.gender === 'woman'
          ? {
              type: Select,
              label: 'Hobbies and Interests (Female)'.placeholder: 'Please choose your interests'.name: 'hobby'.itemProps: {
                itemProps: {
                  rules: [{ required: true.message: 'Please choose your interests'}],}},elProps: {
                options: [{label: 'drawing'.value: 'drawing' },
                  { label: 'singing'.value: 'singing' },
                  { label: 'dancing'.value: 'dancing'},],},} :null; }, {},type: Input.TextArea,
      name: 'desc'.label: 'introduction'.elProps: {
        placeholder: 'Personal Profile'.rows: 4,},itemProps: {
        rules: [{required: true,},],},}, {// Customize render
      render() {
        return (
          <Form.Item>
            <Space>
              <Button htmlType="submit" type="primary">determine</Button>
              <Button htmlType="reset">reset</Button>
            </Space>
          </Form.Item>); }},];return (
    <Form
      form={form}
      onValuesChange={(v)= >{ setData((p) => ({ ... p, ... v })); }} ><FormRender layoutData={layout} />
    </Form>
  );
}
Copy the code

Implement 1 row n column as follows, such as a row of 2 columns (the length of the subarray determines the number of columns, and the length is divisible by 24)

const layout = [
  [
    {
      type: Input,
      label: '11'.placeholder: 'Please enter'.name: '11'}, {type: Input,
      label: '12'.placeholder: 'Please enter'.name: '12',},], [{type: Input,
      label: '21'.placeholder: 'Please enter'.name: '21'}, {type: Input,
      label: '22'.placeholder: 'Please enter'.name: '22',}]];Copy the code

Implement 1 column 2/3/4 as follows

// Cols = 1, 2, 3, 4; // Cols = 1, 2, 3, 4

const layout3 = [];

for (let i = 0; i < 11; i++) {
  layout3.push({
    type: Input,
    label: ` input box${i + 1}`.placeholder: 'Please enter'.name: `name${i}`}); } <FormRender layoutData={layout3} cols={cols}></FormRender>;Copy the code

Configuration instructions

/ / component
export default function FormRenderer({ layoutData, cols }: FormRenderProps) :React.ReactNode;

/ / component props
export declare type FormRenderProps = {
    layoutData: Array<Item>; // A 1/2 dimensional array
    cols: null | 1 | 2 | 3 | 4; // Automatic layout line 1 shows several columns default 1
};

// Array configuration items
exportdeclare type Item = { type? : React.ComponentType | string;// Component type, such as Input, etcname? : string;/ / Form. The name of the Itemlabel? : string;/ / Form. The Item's labelrender? :() = > React.ReactNode; // Customize rendergetJSON? :() = > Item | null; // Dynamically returns the Item configurationelProps? : Record<string, unknown>;// The component props configuration, for example, if type is Input, elProps is configured to InputitemProps? : Record<string, unknown>;// props for form. Item (name,lable,rulesrules? : Rule[];/ / Form. The rules of the Item
};

Copy the code

Run yarn start/NPM start to view demo. The result is as follows