Git Product home page Git Product logo

antd-form-builder's Introduction

NOTE: this project has been deprecated in favor of: https://github.com/eBay/nice-form-react

antd-form-builder

The FormBuilder is a small helper (< 500 lines of source code) for building forms with React and ant.design easily while not preventing you from using the original antd form API. It can not only be used as editable form but also to display readonly information with form layout. It supports both antd v3 and v4 versions.

NPM JavaScript Style Guide MIT licensed

Examples

You can see the live demo at: https://rekit.github.io/antd-form-builder

Philosophy

The key principle in my mind to create antd-form-builder is it should just help to define form fields and the layout while it doesn't reduce the flexibility of antd's original form API. So in simple patterns you can create a form very easily but if a form is much complicated you can still use the original form API. You can even use antd-form-builder together with the raw API in a mixed way.

Meta Driven

Besides the simplified API which helps to create form easily, the FormBuilder is also very useful if you have meta driven requirement. For example if your form structure needs to be configurable, the meta could be a pure JSON object which can be easily saved and managed separately.

About the New Form API of Ant.Design v4

The new v4 version of ant.design has been released. The form component has been re-written so some API is not backward-compatible now. One of main reasons why antd re-wrote form is for performance improvment of large and complicated forms. But it also lost some flexibilty of creating dynamic forms. In v3, when any form field is changed, the component will be re-rendered because it's hign-order-component based. But in v4 whenever a field is changed the component in which antd form is used will never re-render. That means it's now impossible to create dynamic forms with such code:

<Form>
  {form.getFieldValue('f1') === 'foo' && <Form.Item {...}/>}
</Form>

Instead, you will need similar code like below:

<Form form={form}>
  <Form.Item label="Field1" name="f1">
    <Input />
  </Form.Item>
  <Form.Item shouldUpdate>
    {() =>
      form.getFieldValue("f1") === "foo" && (
        <Form.Item label="Field2" name="f2">
          <Input />
        </Form.Item>
      )
    }
  </Form.Item>
</Form>

Then when you type in 'foo' in the field1 the second field2 will appear.

IMO, this API design looks hacky because the nested form item here is meaningless but the outer one is just a wrapper. The new shouldUpdate property means when other fileds are changed the Form.Item will re-render.

The new API is much less flexible for dynamic forms because you have to put all dynamic logic in the render props. That also means you have to break components into small parts that update separately in render props.

So for the new form API of antd v4, the antd-form-builder can still save much time for building dynamic forms.

New API for antd v4

Though the API of antd-form-builder is backward-compatible, the new form builder still increases the major version to 2.0 since there're some new API for antd v4. If you still use antd v3 you don't need to change any code after upgrading to form builder 2.0. If you use v4, below is the key difference.

1. For class components

You need to create a form instance by FormBuilder.createForm() and pass it to the antd's Form:

import FormBuilder from 'antd-form-builder';

export default class App extends Component {
  formRef = React.createRef()
  render() {
    const meta = [{ key: 'name', label: 'Name' }]
    return (
      <Form ref={formRef} onValuesChange={() => this.forceUpdate()}>
        <FormBuilder meta={meta} form={this.formRef} />
      </Form>
    )
  }
}

2. For functional components

You need to create a form with the hook Form.useForm() and pass it to the antd's Form:

import { Form } from 'antd';
import FormBuilder from 'antd-form-builder'

export default () => {
  const [form] = Form.useForm()
  const forceUpdate = FormBuilder.useForceUpdate();
  const meta = [{ key: 'name', label: 'Name' }]
  return (
    <Form form={form} onValuesChange={forceUpdate}>
      <FormBuilder meta={meta} form={form} />
    </Form>
  )
}

3. Pass forceUpdate to antd's Form's onValuesChange

This is because in the v4 Form, when fields are changed, the component is not re-renderred. This "urgly" mechanism ensure the wrapper component is always re-renderred when fields change. The reason why it took a bit long time for the FormBuilder 2.0 is just I also think this API looks a bit stange but unitl now I've not found a better way. However you don't need to worry about using this API because it will not bring incompatabilty issue. However, if you don't need dynamic field capability, you don't need to do this. If you want to control the dynamic logic more flexible, you can use shouldUpdate with Form.Item yourself.

Install

npm install --save-dev antd-form-builder

Usage

The most simple usage is like below (for antd v4):

import React from 'react'
import { Form, Button } from 'antd'
import FormBuilder from 'antd-form-builder'

export default () => {
  const [form] = FormBuilder.useForm()
  const meta = {
    fields: [
      { key: 'username', label: 'User Name' },
      { key: 'password', label: 'Password', widget: 'password' },
    ],
  }

  return (
    <Form form={form}>
      <FormBuilder meta={meta} form={form} />
      <Form.Item wrapperCol={{ span: 16, offset: 8 }}>
        <Button type="primary">Login</Button>
      </Form.Item>
    </Form>
  )
}

Then you get a form:

To see more examples, please go to https://rekit.github.io/antd-form-builder

NOTE: if you use antd v3.x, you may see a warning about module not found: rc-field-form/lib/useForm. It's not a problem because it needs to dynamically detect if the current Form is v3 or v4. If you know a better way without warning, feel free to create a PR.

API Reference

The FormBuilder could be used for both antd v3 and v4, but the API set has a little difference. They will be marked as and .

General API for antd v4

Name Description
FormBuilder.useForceUpdate If you need dynamic form, that is meta is changed when fields value changed, you should manually trigger re-render by set onValuesChange={forceUpdate}.

FormBuilder

Props:

Name Type Description
form object The antd form instance, unnecessary in viewMode
meta object/array The meta for building the form. See below docs for detailed usage
getMeta(form, props) function You can pass a function to get form meta rather than give meta object directly. This is new from v2.1.0.
viewMode bool In view mode, FormBuild uses viewWidget property for a field, show value directly if viewWidget not defined. And labels are left aligned in the form. Default to false.

meta

meta property tells FormBuilder about all information of form structure. Its basic structure is like below:

const meta = {
  columns: 2, // how many columns to layout fields
  fields: [], // which fields in form
};

If meta is an array, it will be used as fields:

const realMeta = { fields: meta }

If meta is an object without fields property, it's treated as a single field meta, so it will be converted to:

const realMeta = { fields: [meta] }

Properties are listed below:

Name Type Default Description
columns number 1 How many columns of the form layout.
formItemLayout object/array [8, 16] The labelCol and wrapperCol passed to Form.Item. If it's an array, will be converted to { labelCol: { span: arr[0] }, wrapperCol: { span: arr[1] }}. If a filed has different layout, define it in its own meta.
viewMode bool In view mode, FormBuild uses viewWidget property for a field, show value directly if viewWidget not defined. And labels are left aligned in the form. Default to false.
disabled bool false If true, all fields components will be given a disabled property.
initialValues object null Set initialValues to the form, usually used in form which edit values or in viewMode. You can also set initialValue for each field.
fields array null Fields definition for the form. See below info for how to define a field.
gutter number 0 The gap between columns.

Field meta

Field meta is used to define each field. Each field meta is an object defined in meta.fields. It's a central place to combine parameters to FormBuilder itself, <Form.Item> and getFieldDecorators. All options are listed below:

Name Type Default Description
key string Required. The field key. Could be nested like user.name.last. It's just the key value passed to getFieldDecorator(key, options)
name string/array Alternative of key. In form v4, if you need nested property for colleced form values like : { name: { first, last } } you can define an array for the name property: ['name', 'first']. If you prefer name.first, use key to define it.
label string Label text.
viewMode bool false Whether the field is in view mode. Note if a field is in viewMode but FormBuilder is not, the label in the field is still right aligned.
readOnly bool false Whether the field is readOnly. The difference compared to viewMode is a read-only field is managed by form that is the value is collected when use form.getFieldsValue, but viewMode is not. It is also validated if some rules are configured for the field.
tooltip string/React Node If set, there is a question mark icon besides label to show the tooltip.
widget string/Component Input Which component used to render field for editing. The component should be able to be managed by antd form.
widgetProps object Props passed to widget.
viewWidget string/Component text Which component used to render field in view mode.
viewWidgetProps object Props passed to viewWidget
formItemLayout object/array [8, 16] This applies formItemLayout only to this field rather than which defined in the root meta.
render function If provided, this is used for rendering the whole field in both edit and view mode, should render <Form.Item>, getFieldDecorator itself. widget property will be ignored.
renderView function If provided, this is used for rendering field value in view mode, viewWidget will be ignored.
colSpan number 1 How many columns the field should take up.
initialValue any The initialValue to be passed to the field widget. In view mode, it's the value to be display.
getInitialValue func(field, initialValues, form) Get the initialValue of the field. This may be used to combine multiple fields into one field
disabled bool false If set to true, every widget in field will be given a disabled property regardless of if it's supported.
clear enum In multiple columns layout, used to clear left, right or both side fields. Like the clear property in css. Could be left: the field starts from a new row; right: no fields behind the field; both: no other fields in the same row.
forwardRef bool If your field widget is a funcional component which doesn't implement forwardRef, set this to true so that React doesn't prompt warning message.
noFormItem bool false By default, each field is wrapped with <Form.Item>, if set to true, it just use getFieldDecorators.
noStyle bool false The same with old noFormItem. Provlide the alias noStyle to be consitent with antd v4.
children ReactNode The children of widget defined in meta.
required bool false Whether the field is required.
message string If a field is required, you can define what message provided if no input. By default, it's ${field.label} is required.
options array Only used by select, radio-group. checkbox-group components, explained below.
formItemProps object The props passed to <Form.Item>. Below properties are short way to pass props to <Form.Item>. See more from antd's doc
colon bool true Used with label, whether to display : after label text.
extra string/ReactNode The extra prompt message. It is similar to help. Usage example: to display error message and prompt message at the same time.
hasFeedback bool false Used with validateStatus, this option specifies the validation status icon. Recommended to be used only with Input.
help string/ReactNode The prompt message. If not provided, the prompt message will be generated by the validation rule.
htmlFor string Set sub label htmlFor.
labelCol object The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with <Col>.
validateStatus string The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating'
wrapperCol object The layout for input controls, same as labelCol.
fieldProps object The options to pass to getFieldDecorator(id, options). Below properties are short way to pass options to getFieldDecorator(id, options). See more from antd's doc
getValueFromEvent function(..args) Specify how to get value from event or other onChange arguments
getValueProps function(value) Get the component props according to field value.
normalize function(value, prevValue, allValues) Normalize value to form component
preserve bool false Keep the field even if field removed.
rules object[] Includes validation rules. Please refer to "Validation Rules" part for details.
trigger string 'onChange' When to collect the value of children node
validateFirst bool false Whether stop validate on first rule of error for this field.
validateTrigger string / string[] 'onChange' When to validate the value of children node.
valuePropName string Props of children node, for example, the prop of Switch is 'checked'.

Use String Key to Define a Widget

To define the widget for a field, you can use either a string which maps to a widget or a react component directly.

const meta = { key: 'name', label: 'Name', widget: 'input'}
// or
const meta = { key: 'name', label: 'Name', widget: Input }

The reason why you can use a string for widget property is because there are some key-component mapping pre-defined in antd-form-builder/defineAntdWidget.js. Normally you can use a component for widget/viewWidget property of a field meta, but sometimes it's more convenient to use string so that you don't need to import the component while defining meta. And it's especially useful if you want to save meta in some config json.

The predefined components are listed below:

key Component meta convention
input Input
password Input.Password
textarea Input.TextArea
number InputNumber
select Select Typically you need to provide children property for Option array to the field meta metioned above. To make it easy to use, you can provide an options array to the field meta, internally it will be convented to children property. Explained below.
date-picker DatePicker
radio Radio
checkbox CheckBox
checkbox-group CheckBox.Group Use options for children, same as select.
switch Switch
radio-group Radio.Group Use options for children, same as select. Also you can set buttonGroup to true for tab button style instead of radio style.
button Button

options

options is a special field meta just mentioned. It's only used for select, checkbox-group or radio-group. You can define children by options in 3 formats:

  1. [opt1, opt2, opt3, ...], here value and label are same as opt1, opt2, opt3....

  2. [[value1, label1], [value2, label2], ...]

  3. [{value: 'v1', label: 'label1'}, {value: 'v2', label: 'label2'}, ...]

Extend FormBuilder: Define Keys for Your Components

Besides built-in pre-defined components, you can define your own by FormBuilder.defineWidget static method like below:

const MyComp = ({ value, onChange}) => {...}
FormBuilder.defineWidget('my-comp', MyComp)

Then you can use it:

const meta = { key: 'comp', label: 'Comp', widget: 'my-comp' }

This mechanism not only makes it easy to define meta easily in your project, but also useful if you want your meta could be pure JSON object.

FormBuilder.defineWidget(key, component, metaConvertor)

Define the key for a widget so that you can use string key in the meta like 'date-picker', 'select'. You can also provide a meta convertor to to provide easier way to give props to the widget.

key

string key to used for the widget

component :

The react component to used in form field

metaConvertor

function, convert field meta to a new meta.

For example: to make it easier to define a Select widget for the field, FormBuilder uses below code internally:

const mapOptions = options => {
  if (!_.isArray(options)) {
    throw new Error('Options should be array in form builder meta.')
  }
  return options.map(opt => {
    if (_.isArray(opt)) {
      return { value: opt[0], label: opt[1] }
    } else if (_.isPlainObject(opt)) {
      return opt
    } else {
      return { value: opt, label: opt }
    }
  })
}

FormBuilder.defineWidget('select', Select, field => {
  if (field.options && !field.children) {
    return {
      ...field,
      children: mapOptions(field.options).map(opt => (
        <Select.Option value={opt.value} key={opt.value}>
          {opt.label}
        </Select.Option>
      )),
    }
  }
  return field
})

Then you can define options for select component with below meta:

const meta = { key: 'select', label: 'Select', options: ['opt1', 'opt2']}

Here options property from meta is converted to chilren property to Select component. You can define options in two mode:

[[value1, label1], [value2, label2]]
// or
[valueAndLabel1, valueAndLabel2]

Otherwise without metaConvertor, you have to define your meta like below:

const meta = {
  key: 'select',
  label: 'Select',
  children: ['opt1', 'opt2'].map(key => <Option key={key}>{key}</Option>),
};

So if you define you own widget, you can give a metaConvertor to provide a convenient way to define field widget.

Contribute

Local development

This project is bootstraped by create-react-library. To start development locally, follow below steps:

# 1. Clone repo
git clone https://github.com/rekit/antd-form-builder.git

# 2. Install dependencies
cd antd-form-builder
npm install

# 3. Run rollup in watch mode
npm start

# 4. Start example dev server (in anther tab)
cd example
npm start

Now, anytime you make a change to your library in src/ or to the example app's example/src, create-react-app will live-reload your local dev server so you can iterate on your component in real-time.

Build examples

cd examples
npm run build

This will build examples into root docs folder which is used as gh-pages root. So after build, commit or pr the changes to the repo.

License

MIT © supnate

antd-form-builder's People

Contributors

afeiship avatar ariesjia avatar dependabot[bot] avatar gableroux avatar mertcan avatar onur-saf avatar sivacohan avatar smddzcy avatar spadarshut avatar supnate avatar wilds avatar yijzhu avatar ziegfiroyt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

antd-form-builder's Issues

In wizard, how do you add label and dynamic fields?

From your Wizard example, I am trying to add a dynamic form.
In my form when i click a checkbox, it successfully creates the label and field but I get the error:
index.js:1437 Warning: Each child in a list should have a unique "key" prop. See https://fb.me/react-warning-keys for more information

My code:

if (form.getFieldValue('random') === true) {

var keys = [
  {
    key: 'random-label',
    colSpan: 2,
    render() {
      return (
        <fieldset>
          <legend>Random Header</legend>
        </fieldset>
      )
    },
  },
  {
    key: 'random-field', 
    label: 'Random', 
    //required: true 
  },
];

keys.forEach(key => {
  newWizardMeta.steps[1].formMeta.fields.push(key);
});
}

Input.Group compact support?

How to implement a case when one field consists of two fields?
In plain antd form I would do something like this:

    <Input.Group compact>
      <Input style={{ width: '20%' }} defaultValue="0571" />
      <Input.Search style={{ width: '30%' }} defaultValue="26888888" />
    </Input.Group>

But how to do it with antd-form-builder?

Default error message for required fields

antd-form-builder is providing a hardcoded error message which is not localized. It should leave that to Ant Design so the users can define their own default error messages using Ant Design's ConfigProvider.

Set formItemLayout, but displaying form as vertical.

Dear supnate,

Thank you for sharing this antd-form-builder!

I'm trying to adjust the column width using formItemLayout.
As in the API Reference, the default value of formItemLayout is [8, 16]. I tried to set it as

const meta = {
      formItemLayout:[6, 18],
      fields: [{...}]
}

Or

const meta = {
     formItemLayout:{
        labelCol: { span: 8 },
        wrapperCol: { span: 16 },
      },
      fields: [{...}]
}

But the form layout will change to vertical, not displaying as horizontal form.

Could you give a working example of set the column width?

Thank you very much!

Nest for values as array

Unable to use nested array values.
https://codesandbox.io/s/autumn-butterfly-8tubs?file=/src/DynamicFields.js

Example. If we are trying to nest as objs
if we take A.B and A.C as 2 keys in the form with both being inputs, and have submit the form then we get the result as

A: {
    B: 'value B',
    C: 'value C`
}

But unable to do the same for array,

A[0].B and A[1].C are the keys and while submitting.

{
    A[0]: {
        B: "value B"
    },
    A[1]: {
        C: 'value C',
    }
}

Instead of being this way

{
    A: [
        B: "value B",
        C: "value C"
    ]
}

Tabs support

Hi,

I just discover you package and it seems to be very helpful ! It's exactly what I'm looking for !

I saw we can set Wizard form and it's very fine, and I think i will be a great idea to support Tabs too !

Thanks for your work !

onSeach in select widget

I'm trying to adapt this example such that select widget allows for REST API query but it doesn't seem to fire onSearch event. This is my attempt (posting just the necessary changes)

This part is from the antd's search box

import {useState, useEffect} from "react"
import axios from "axios"
import querystring from 'querystring';

let timeout;
let currentValue;

function QueryFetch(url, value, initialState, callback) {
    const [data, setData] = useState(initialState);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);

    if (timeout) {
        clearTimeout(timeout);
        timeout = null;
    }
    currentValue = value;

    const str = querystring.encode({
        code: 'utf-8',
        q: value,
    });

    useEffect(() => {
        setLoading(true)

        async function fetchData() {
            try {
                const res = await axios.get(`url?${str}`);
                if (currentValue === value) {
                    console.log(res.data);
                    setData(res.data.results);
                    callback(data);
                }
            } catch (error) {
                setError(error.message);
            } finally {
                setLoading(false);
            }
        }

        timeout = setTimeout(fetchData, 300);
    }, [url, str, data, callback, value])
}

export {QueryFetch}

and this is what I'm trying to do

const [cities, setCities] = useState({})

{
      key: 'city',
      label: 'City',
      widget: 'select',
      options: country ? citites : [],
      placeholder: loading ? 'Loading...' : 'Select city...',
      widgetProps: { loading },
      disabled: loading || !country,
      widgetProps: {
                                options: cities.map(
                                    c => <Option key={f.id} value={f.name}>{f.name}</Option>
                                ),
                                showSearch: true,
                                onSearch: () => {
                                    console.log("Entered search")
                                    QueryFetch(
                                        "http://localhost:8000/cities/",
                                        form.getFieldsValue("country"),
                                        null,
                                        data => setCities(data)
                                    )
                                },
                            }
    },

demo request : Add new field

Hello,

Could you show an example where we can push a new item to an array in the form:

ex:
"Add New Fruit:" this a a new fruit a i am adding to the list ||| "Press button to Add Another fruit "

so we could add new values to an array dynamically.

Yours,

Marc

css misbehavior

Screen Shot 2020-03-27 at 11 39 23 AM

Problems:

  1. components show the wrong style. When I put above submit button, all on this page show the correct style.

  2. Before click , all components works correct. After click , the red rectangle area won't response to any click event. It seems like something invisible musk that area.

Environment:

package.json

{
  "private": true,
  "scripts": {
    "start": "umi dev",
    "build": "umi build",
    "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
    "test": "umi-test",
    "test:coverage": "umi-test --coverage"
  },
  "gitHooks": {
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,jsx,less,md,json}": [
      "prettier --write"
    ],
    "*.ts?(x)": [
      "prettier --parser=typescript --write"
    ]
  },
  "dependencies": {
    "@types/classnames": "^2.2.10",
    "@umijs/preset-react": "1.x",
    "@umijs/test": "^3.0.13",
    "antd-form-builder": "file:./vendors/antd-form-builder",
    "classnames": "^2.2.6",
    "lint-staged": "^10.0.7",
    "prettier": "^1.19.1",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "umi": "^3.0.13",
    "yorkie": "^2.0.0"
  }
}

config/config.ts

import { defineConfig } from 'umi';
import {routes} from './router';

export default defineConfig({
  targets: {
    ie: 11,
  },
  locale: {
    default: 'zh-CN',
    antd: true,
    baseNavigator: true,
    baseSeparator: '-',
  },
  layout: {
    title: 'Da Vinci',
    locale: true,
  },
  antd: {},
  dva: {hmr: true},
  dynamicImport: {
    loading: '@/components/PageLoading',
  },
  routes,
});

running demo
your example-v4/basic.js

import React, { Component } from 'react'
import { Form, Button, Rate, Card, InputNumber, DatePicker } from 'antd'
// @ts-ignore
import FormBuilder from 'antd-form-builder'

export default class App extends Component {
  form = FormBuilder.createForm(this);
  handleFinish = () => {
    console.log('submit: ', this.form.getFieldsValue())
  };

  render() {
    const options = ['Apple', 'Orange', 'Banana']
    const meta = {
      columns: 1,
      dynamicFields: '*',
      fields: [
        {
          key: 'obj.input',
          label: 'Input',
          required: true,
          tooltip: 'This is the tooltip.',
        },
        { key: 'checkbox', label: 'Checkbox', widget: 'checkbox', initialValue: true },
        { key: 'rating', label: 'Rating', widget: Rate, initialValue: 2 },
        { key: 'switch', label: 'Switch', widget: 'switch', initialValue: true },
        { key: 'select', label: 'Select', widget: 'select', options },
        { key: 'checkbox-group', label: 'Checkbox Group', widget: 'checkbox-group', options },
        { key: 'radio-group', label: 'Radio Group', widget: 'radio-group', options },
        {
          key: 'radio-button-group',
          label: 'Radio Button Group',
          widget: 'radio-group',
          buttonGroup: true,
          options,
        },
        { key: 'password', label: 'Password', widget: 'password' },
        { key: 'textarea', label: 'Textarea', widget: 'textarea' },
        { key: 'number', label: 'Number', widget: 'number' },
        { key: 'date-picker', label: 'Date Picker', widget: 'date-picker' },
      ],
    }

    return (
      <Card>
      <Form
        form={this.form}
        layout="horizontal"
        onValuesChange={this.form.handleValuesChange}
        onFinish={this.handleFinish}
      >
        <FormBuilder meta={meta} form={this.form} />
        <Form.Item wrapperCol={{ span: 16, offset: 8 }} className="form-footer">
          <Button htmlType="submit" type="primary" onClick={() => this.forceUpdate()}>
            Submit
          </Button>
        </Form.Item>
      </Form>
      </Card>
    )
  }
}

useForceUpdate does not exists on type

I got this error below after updating to the latest version, may I know if there any fix for the TypeScript?

Error

Type error: Property 'useForceUpdate' does not exist on type 'FC<FormBuilderInterface> & { defineWidget: (key: string, component: any, metaConvertor?: (field: FieldType) => FieldType) => void; }'.

const [form] = Form.useForm();
const forceUpdate = FormBuilder.useForceUpdate();
                               ^

Usage

const [form] = Form.useForm();
const forceUpdate = FormBuilder.useForceUpdate();

Temporary solution (Quick Fix)
Anyone facing this issue can actually pin the version to 2.1.2 since version 2.1.3 only added the TypeScript feature.

colSpan support responsive config

currently colSpan is only support fixed number config.
Is it possible to support responsive config ? like
colSpan = { sm: 1, md: 2, }

can support upload?

i use antd upload in formbuilder, it is ok when upload's props fileList is undefined, but if give an empty array, the upload works bad, uploaded image cann't display. thanks

Embed icon

Hi,

Is there any way to embed an icon , like this vanilla antd example?

<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
             placeholder="Username"  />

from https://ant.design/components/form

表单数据如何反显

调用, form.setFieldsValue 报错:
Warning: You cannot set a form field before rendering a field associated with the value.
请问有没有相关api, 可以反显表单数据

Cannot pass formatter to Input

I would like to use formatter and parser function to <Input> or <InputNumber /> components, but can't figure out how to pass those functions.
I've tried adding them to widgetProps, or getValueProps but that didn't seem to work.
Thanks!

TS Error : Property 'useForm' does not exist on type 'FC<FormBuilderInterface>

just installed and copied example code

npm install --save-dev antd-form-builder

import React from 'react'
import { Form, Button } from 'antd'
import FormBuilder from 'antd-form-builder'

export default () => {
  const [form] = FormBuilder.useForm()
  const meta = {
    fields: [
      { key: 'username', label: 'User Name' },
      { key: 'password', label: 'Password', widget: 'password' },
    ],
  }

  return (
    <Form form={form}>
      <FormBuilder meta={meta} form={form} />
      <Form.Item wrapperCol={{ span: 16, offset: 8 }}>
        <Button type="primary">Login</Button>
      </Form.Item>
    </Form>
  )
}

Import Icon

HI, i just switch to [email protected] and i got this error

./node_modules/antd-form-builder/dist/index.es.js
Attempted import error: 'Icon' is not exported from 'antd'.

And i'm using [email protected]

How can i fix it ?

Thanks

Adding new components like antd 4 Upload

Antd version 4 components are basically added on Antd-Form-Builder Rekit. But, other components like photo upload added as a custom component. Form Builder deletes all duplicated codes on so many forms. For the eliminating custom components, this issue opened for adding antd 4 Upload component like as select, input ext..
https://ant.design/components/upload/

Antd <ConfigProvider /> Support?

Creating forms with FormBuilder Component is a breeze but it seems to not take advantage of ant's ConfigProvider component props/settings. I'm using locale prop with and it translates every other every component but FormBuilder generated forms.

There are any workaround for this?


That's my Wrapper:

import ptBR from "antd/lib/locale/pt_BR";

const AdminWrapper = (props) => {
  message.config({ maxCount: 1 });

  return (
    <ConfigProvider locale={ptBR}>
        <NewChildren {...props} />
    </ConfigProvider>
  );
};

FormBuilder example:

<Form
    form={form}
    layout="vertical"
    onFinish={handleFinish}
    validateMessages={validateMessages}
>
    <Tabs tabPosition="top">
       <Tabs.TabPane tab="Usuario" key="1">
          <FormBuilder form={form} meta={meta1} initialValues={...} />
        </Tabs.TabPane>

        <Tabs.TabPane tab="Filiacao" key="2">
           <FormBuilder form={form} meta={meta2} initialValues={...} />
        </Tabs.TabPane>
    </Tabs>
</Form>

Validation Message object used as

prop:

export default {
  required: "Campo obrigatorio",

  types: {
    email: "E-mail invalido",
    number: "Numero invalido"
  }
};

getFieldValue not working with custom component

I'm writing a page for creating an opinion about a particular thing and I'd like it to be a multi-step form with coordinated Select components, each querying different REST API endpoint. I tried to combine several things: antd's Search Box as well as antd-form-builder's Wizard. However, from what I can understand from the source code of antd-form-builder, onSearch is not supported in the select components so I had to make my own custom one that would be able to query REST API. Currently, I have the following code (this is essentially a copy-paste of the Wizard example linked above with a custom search box component from antd website linked above):

import React, {useState, useCallback} from "react";
import FormBuilder from "antd-form-builder";
import {Form, Button, Steps, Select} from 'antd'
import querystring from "querystring";
import axios from "axios";
import {api} from "../api";

const {Option} = Select;
const {Step} = Steps

let timeout;
let currentValue;

function fetch(url, value, query, callback) {
    if (timeout) {
        clearTimeout(timeout);
        timeout = null;
    }
    currentValue = value;

    async function getData() {
        if (currentValue === value) {
            const str = querystring.encode({
                name: value,
                q: query,
            });
            const res = await axios.get(`${url}?${str}`);
            callback(res.data);
        }
    }

    timeout = setTimeout(getData, 500);
}

const SearchInput = ({url, query, ...props}) => {
    const [data, setData] = useState([]);
    const [value, setValue] = useState(props.initialValue);

    const handleSearch = value => {
        if (value) {
            console.log(query)
            fetch(url, value, query, result => {
                setData(result);
            });
        } else {
            setData([]);
        }
    };

    const handleChange = value => {
        setValue(value)
    };

    return (
        <Select
            showSearch
            value={value}
            defaultValue={props.defaultValue? props.defaultValue : undefined}
            disabled={props.disabled}
            showArrow={true}
            style={{fontSize: "large"}}
            filterOption={false}
            onSearch={handleSearch}
            onChange={handleChange}
        >
            {data.map(d => <Option style={{fontSize: "large"}} key={d.id} value={d.name}>{d.name}</Option>)}
        </Select>
    );
}

FormBuilder.defineWidget('search-input', SearchInput)

const OpinionsCreate = () => {
    const [form] = Form.useForm()
    const [currentStep, setCurrentStep] = useState(0)
    const forceUpdate = FormBuilder.useForceUpdate()
    const handleFinish = useCallback(() => {
        console.log('Submit: ', form.getFieldsValue(true))
    }, [form])

    const wizardMeta = {
        steps: [
            {
                title: 'Strona wysyłająca',
                formMeta: {
                    columns: 1,
                    fields: [
                        {
                            key: 'sending_university',
                            required: true,
                            disabled: true,
                            widget: "search-input",
                            widgetProps: {
                                initialValue: 'Some university',
                                url: api.endpoint1,
                                defaultValue: "Some university",
                                onChange: () => {
                                    // Clear sending_faculty value when country is changed
                                    form.setFieldsValue({sending_faculty: undefined})
                                },
                            }
                        },
                        {
                            key: 'sending_faculty',
                            required: true,
                            widget: "search-input",
                            widgetProps: {
                                url: api.endpoint2,
                                query: form.getFieldValue("sending_university"),
                            }
                        },
                    ],
                },
            },
        ]
    }

    // Clone the meta for dynamic change
    const newWizardMeta = JSON.parse(JSON.stringify(wizardMeta))
    // In a wizard, every field should be preserved when swtich steps.
    newWizardMeta.steps.forEach(s => s.formMeta.fields.forEach(f => (f.preserve = true)))

    // Generate a general review step
    const reviewFields = []
    newWizardMeta.steps.forEach((s, i) => {
        reviewFields.push(
            {
                key: 'review' + i,
                colSpan: 2,
                render() {
                    return (
                        <fieldset>
                            <legend>{s.title}</legend>
                        </fieldset>
                    )
                },
            },
            ...s.formMeta.fields,
        )
    })

    newWizardMeta.steps.push({
        key: 'review',
        title: 'Podsumowanie',
        formMeta: {
            columns: 2,
            fields: reviewFields,
        },
    })

    const stepsLength = newWizardMeta.steps.length

    const handleNext = () => {
        form.validateFields().then(() => {
            setCurrentStep(currentStep + 1)
        })
    }
    const handleBack = () => {
        form.validateFields().then(() => {
            setCurrentStep(currentStep - 1)
        })
    }
    const isReview = currentStep === stepsLength - 1

    return (
        <Form
            layout="horizontal"
            form={form}
            onValuesChange={forceUpdate}
            style={{width: '100%'}}
            onFinish={handleFinish}
        >
            <Steps current={currentStep}>
                {newWizardMeta.steps.map(s => (
                    <Step key={s.title} title={s.title}/>
                ))}
            </Steps>
            <div style={{background: '#f7f7f7', padding: '20px', margin: '30px 0'}}>
                <FormBuilder
                    viewMode={currentStep === stepsLength - 1}
                    form={form}
                    meta={newWizardMeta.steps[currentStep].formMeta}
                />
            </div>
            <Form.Item className="form-footer" style={{textAlign: 'right'}}>
                {currentStep > 0 && (
                    <Button onClick={handleBack} style={{float: 'left', marginTop: '5px'}}>
                        Back
                    </Button>
                )}
                <Button>Cancel</Button>&nbsp; &nbsp;
                <Button type="primary" onClick={isReview ? () => form.submit() : handleNext}>
                    {isReview ? 'Submit' : 'Next'}
                </Button>
            </Form.Item>
        </Form>
    )
}

export default OpinionsCreate;

There are several things going on here

  1. the first select has a default value, and I'd like this select to be disabled such that this default value couldn't be changed (I don't exactly know how I can achieve this, but I tried)
  2. the second select (in theory) should access the value in the first select, and query the backend endpoint api.endpoint2, which is a URL, with an additional ?q=... parameter, such that only certain options would be returned (the second select is coordinated)
  3. In the SearchInput there is a handleSearch method that fires whenever the user types something in the text input. The backend is queried such that the value from the first select is taken into account as well as the text typed by the user is contained in the retrieved options.

The custom search component is working alright itself, however, I stumbled upon several issues:

  1. I can't get the value from the first select by using form.getFieldValue("sending_university") so the second select queries just entire table in the database which is not what I wanted. It somehow doesn't recognize that there's an initial value in the first component.
  2. Even when I add a third select component, dependent on the second one, it doesn't recognize the value selected in the second component either.
  3. When I try to continue to the next step, validation fails, claiming that all fields' values are empty, even the first one with the default initial value.

I tried many combinations all to no avail. I'm pretty sure, what I want is doable but I'm already short of ideas. Does anyone see what's the issue and can help me out?

InitialValues not working anymore

HI,

I just switch from Antd@3 to Antd@4 and i upgrade your module.

I change my code to fit new specs but when I provide initialValues, there are not more informations in my fields.

Uncaught TypeError: form.getFieldsValue is not a function

"antd": "^4.16.13"


    const personalInfo = {
      name: { first: 'Nate', last: 'Wang' },
      email: '[email protected]',
      gender: 'Male',
      dateOfBirth: moment('2100-01-01'),
      phone: '15988888888',
      city: 'Shanghai',
      address: 'No.1000 Some Road, Zhangjiang Park, Pudong New District',
    }

    const meta = {
      columns: 2,
      fields: [
        { key: 'name.first', label: 'First Name' },
        { key: 'name.last', label: 'Last Name' },
        { key: 'gender', label: 'Gender' },
        {
          key: 'dateOfBirth',
          label: 'Date of Birth',
        },
        { key: 'email', label: 'Email' },
        { key: 'phone', label: 'Phone' },
        { key: 'address', label: 'Address', colSpan: 2 },
        { key: 'city', label: 'City' },
        { key: 'zipCode', label: 'Zip Code' },
      ],
    }

render () {

return (
 <Drawer
        visible={true}
        footer={
          <div style={{ textAlign: 'left' }}>
            <Button onClick={onClose} style={{ marginRight: 8 }}>
              Close
            </Button>
          </div>
        }
      >
        <div>
          <FormBuilder meta={meta} form={this.formRef} initialValues={personalInfo} viewMode />

        </div>

      </Drawer>
    );

  }

how do I get access to the data??

Hello, I am sooo confused by the example, I simply want to have access to the data and all I get is Object object...

What am I doing wrong?

Although it shows in the console.log, I tried so many ways and spent so many hours to get access to the data. How can I do it?

Yours,

here is my codesandbox : https://codesandbox.io/s/elastic-gates-ncib1?file=/src/App.js

in CodeSandbox, I also get this in the console:

Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Wave which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://fb.me/react-strict-mode-find-node
in button (created by Button)
in Wave (created by Button)
in Button (at App.js:21)
in div (created by FormItemInput)
in div (created by FormItemInput)
in div (created by Context.Consumer)
in Col (created by FormItemInput)
in FormItemInput (created by FormItem)
in div (created by Context.Consumer)
in Row (created by FormItem)
in FormItem (at App.js:20)
in form (created by ForwardRef(Form))
in ForwardRef(Form) (created by ForwardRef(InternalForm))
in SizeContextProvider (created by ForwardRef(InternalForm))
in ForwardRef(InternalForm) (at App.js:18)
in Unknown (at src/index.js:9)
in StrictMode (at src/index.js:8)

Import methods from lodash for tree-shaking

I've noticed FormBuilder.js has import _ from 'lodash' - this will import all of lodash.
You might want to do things like this to enable tree-shaking.

import memoize from 'lodash/memoize;
import isArray from 'lodash/isArray;
import pick from 'lodash/pick;
import capitalize from 'lodash/capitalize;
import has from 'lodash/has;
import get from 'lodash/get;
import find from 'lodash/find;

then just replace _. with ''

https://www.azavea.com/blog/2019/03/07/lessons-on-tree-shaking-lodash/
https://stackoverflow.com/questions/35250500/correct-way-to-import-lodash

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.