English | 中文说明
The project has been migrated to @simpleform/render, this repository is deprecated.
High degree of freedom and Lightweight dynamic form Engine, high-end solutions often require only simple design(which is done based on react-easy-formcore development).
- Component Registration: Registered form components need to match the
value
/onChange
(or other field) pass-through in order to work properly. - Component description:
properties
supports object or array type rendering, Supports adding nested object fields via theproperties
property. - Component rendering:
Form
component handles form values,RenderFormChildren
component handles form rendering, oneForm
component can support multipleRenderFormChildren
components rendering internally. - Component linkage: All form properties can support string expressions to describe linkage conditions (except
properties
).
- v8.x
- Update the underlying components to
react-easy-formcore
at least version 5.x. - Rendering data source
properties
rendering method changed, divided into nested nodes and control nodes, where nested nodes no longer carry form field components, only control nodes carry form field components by default changed tostore
formrender
,
- Update the underlying components to
- v7.x
- 7.0.0 Remove
property, keepcontrols
components
property to register all global components.
- 7.0.0 Remove
- v6.x
- 6.2.17
change tofieldComponent
component
,Thecomponent
property can be set tonull
. and updateFormRenderStore
methods.removed;addItemByIndex
,addAfterByPath
,addBeforeByPath
- 6.2.7 When the default component
RenderForm
reports an error in theform
tag in the nested case, you can settagName
to be replaced by another tag. - 6.2.5 Enhancing and adjusting the usage of string expressions, and adding a new description of how to use string expressions in this document.
- 6.2 adapt the underlying
react-easy-formcore
library to path systems above4.x
, fix theuseFormValues
bug. - The component is split into
Form
andRenderFormChildren
components, theForm
component handles the form values, theRenderFormChildren
renders the form based on the information provided, aForm
component can wrap multipleRenderFormChildren
components, if multipleRenderFormChildren
components have the same properties as each other, the later will override the previous properties are flattened, so you need to useschema
properties
to render the form instead, andneeds to be replaced withonSchemaChange
onPropertiesChange
- 6.2.17
- v5.x:
This update completes the decoupling of the form display component from the form value related logic. basic version.
- The underlying library
react-easy-formcore
is updated, you need to remove the old package and install the new version , onlyreadOnlyItem
is deprecatedreadOnlyRender
is kept
- The underlying library
- v4.x:
v4.x and previous versions mostly adjust some method naming and parameter passing changes.
- Deprecate fixed container properties
andcol
, add custom containerscustomInner
inside
andoutside
; towidgets
,controls
andwidget
towidgetProps
type
andprops
;readOnlyWidget
toreadOnlyItem
;
- Deprecate fixed container properties
- v3.1.x:
- Adjust the
layout
property of form fields, addinline
,labelWidth
properties AdjustonPropertiesChange
of default export component toonSchemaChange
AdjustcustomChild
tocustomInner
- Adjust the
- v3.0.x:
String expressions representing form values changed from$form
to$formvalues
Add$store
to the string expression to represent an instance ofFormRenderStore
, which can get the form related methods and data- If you need to introduce a built-in component (add/remove buttons for lists), you need to
import 'react-easy-formrender/lib/css/main.css'
.
- v2.x:
remove theand instead inject the form valuesdependencies
propertyformvalues
to the widget component automatically.change api ofRenderFormChildren
component
- v1.x:
- Change the method of the form component
Changecomponent
andprops
in schema towidget
andwidgetProps
changerender
in schema toreadOnlyWidget
andreadOnlyRender
- Version matching react-easy-formcore version 1.1.x or higher
npm install react-easy-formrender --save
# or
yarn add react-easy-formrender
1.First register the basic components(Take the [email protected]
UI library as an example)
// register
import RenderFormDefault, { RenderFormChildren as RenderFormChilds, RenderFormProps } from 'react-easy-formrender';
import React from 'react';
import { Input, InputNumber, Checkbox, DatePicker, Mentions, Radio, Rate, Select, TreeSelect, Slider, Switch, TimePicker } from 'antd';
import 'react-easy-formrender/lib/css/main.css'
export * from 'react-easy-formrender';
export const BaseComponents = {
"Input": Input,
"Input.TextArea": Input.TextArea,
"Input.Password": Input.Password,
"Input.Search": Input.Search,
"InputNumber": InputNumber,
"Mentions": Mentions,
"Mentions.Option": Mentions.Option,
"Checkbox": Checkbox,
'Checkbox.Group': Checkbox.Group,
"Radio": Radio,
"Radio.Group": Radio.Group,
"Radio.Button": Radio.Button,
"DatePicker": DatePicker,
"DatePicker.RangePicker": DatePicker.RangePicker,
"Rate": Rate,
"Select": Select,
"Select.Option": Select.Option,
"TreeSelect": TreeSelect,
"Slider": Slider,
"Switch": Switch,
"TimePicker": TimePicker,
"TimePicker.RangePicker": TimePicker.RangePicker
}
export type CustomRenderFormProps = RenderFormProps<any>;
// RenderFormChildren
export function RenderFormChildren(props: CustomRenderFormProps) {
const { components, expressionImports, ...rest } = props;
return (
<RenderFormChilds
options={{ props: { autoComplete: 'off' } }}
components={{ ...BaseComponents, ...components }}
// expressionImports={{ ...expressionImports, moment }}
{...rest}
/>
);
}
// RenderForm
export default function FormRender(props: CustomRenderFormProps) {
const { components, expressionImports, ...rest } = props;
return (
<RenderFormDefault
options={{ props: { autoComplete: 'off' } }}
components={{ ...BaseComponents, ...components }}
// expressionImports={{ ...expressionImports, moment }}
{...rest}
/>
);
}
import { Button } from 'antd';
import React, { useState } from 'react';
import RenderForm, { useFormStore, useFormRenderStore } from './form-render';
export default function Demo5(props) {
const watch = {
'name2': (newValue, oldValue) => {
console.log(newValue, oldValue)
},
'name3[0]': (newValue, oldValue) => {
console.log(newValue, oldValue)
},
'name4': (newValue, oldValue) => {
console.log(newValue, oldValue)
}
}
const properties = {
name1: {
label: "readonly",
readOnly: true,
readOnlyRender: "readonly component",
initialValue: 1111,
hidden: '{{formvalues && formvalues.name6 == true}}',
type: 'Input',
props: {}
},
name2: {
label: "input",
rules: [{ required: true, message: 'input empty' }],
initialValue: 1,
hidden: '{{formvalues && formvalues.name6 == true}}',
type: 'Input',
props: {}
},
name3: {
// type: '',
// props: {},
properties: [{
label: "list[0]",
rules: [{ required: true, message: 'list[0] empty' }],
initialValue: { label: 'option1', value: '1', key: '1' },
type: 'Select',
props: {
labelInValue: true,
style: { width: '100%' },
children: [
{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } },
{ type: 'Select.Option', props: { key: 2, value: '2', children: 'option2' } }
]
}
}, {
label: "list[1]",
rules: [{ required: true, message: 'list[1] empty' }],
type: 'Select',
props: {
labelInValue: true,
style: { width: '100%' },
children: [
{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } },
{ type: 'Select.Option', props: { key: 2, value: '2', children: 'option2' } }
]
}
}]
},
name4: {
// type: '',
// props: {},
properties: {
first: {
label: "first",
rules: [{ required: true, message: 'first empty' }],
type: 'Select',
props: {
style: { width: '100%' },
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
}
},
second: {
label: "second",
rules: [{ required: true, message: 'second empty' }],
type: 'Select',
props: {
style: { width: '100%' },
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
}
}
}
},
name5: {
label: 'name5',
initialValue: { span: 12 },
valueSetter: "{{(value)=> (value && value['span'])}}",
valueGetter: "{{(value) => ({span: value})}}",
type: 'Select',
props: {
style: { width: '100%' },
children: [
{ type: 'Select.Option', props: { key: 1, value: 12, children: 'option1' } },
{ type: 'Select.Option', props: { key: 2, value: 6, children: 'option2' } },
{ type: 'Select.Option', props: { key: 3, value: 4, children: 'option3' } }
]
}
},
name6: {
label: 'checkbox',
valueProp: 'checked',
initialValue: true,
rules: [{ required: true, message: 'checkbox empty' }],
type: 'Checkbox',
props: {
style: { width: '100%' },
children: 'option'
}
},
}
const form = useFormStore();
// const formRenderStore = useFormRenderStore()
const onSubmit = async (e) => {
e?.preventDefault?.();
const result = await form.validate();
console.log(result, 'result');
};
return (
<div>
<RenderForm
form={form}
// formrender={formRenderStore}
properties={properties}
watch={watch} />
<div style={{ marginLeft: '120px' }}>
<Button onClick={onSubmit}>submit</Button>
</div>
</div>
);
}
The form engine also supports multiple RenderFormChildren
components to render and then the Form
component to handle the form values in a unified manner.
useFormStore
: hook to provide a class for form value processing. provided by the default component itself, or passed in by external props.useFormRenderStore
: hook to provide a class for form rendering, provided by the default component itself, or passed in by external props.
import React, { useState } from 'react';
import RenderForm, { RenderFormChildren, Form, useFormStore, useFormRenderStore } from './form-render';
import { Button } from 'antd';
export default function Demo(props) {
const properties1 = {
part1: {
label: "part1input",
rules: [{ required: true, message: 'empty' }],
initialValue: 1,
type: 'Input',
props: {}
},
}
const properties2 = {
part2: {
label: "part2input",
rules: [{ required: true, message: 'empty' }],
initialValue: 1,
type: 'Input',
props: {}
},
}
const form = useFormStore();
// const formRenderStore1 = useFormRenderStore()
// const formRenderStore2 = useFormRenderStore()
const onSubmit = async (e) => {
e?.preventDefault?.();
const result = await form.validate();
console.log(result, 'result');
};
return (
<div style={{ padding: '0 8px' }}>
<Form form={form}>
<div>
<p>part1</p>
<RenderFormChildren
// formrender={formRenderStore1}
properties={properties1}
/>
</div>
<div>
<p>part2</p>
<RenderFormChildren
// formrender={formRenderStore2}
properties={properties2}
/>
</div>
</Form>
<div style={{ marginLeft: '120px' }}>
<Button onClick={onSubmit}>submit</Button>
</div>
</div>
);
}
support render array
import React, { useState } from 'react';
import RenderForm, { useFormStore } from './form-render';
import { Button } from 'antd';
export default function Demo(props) {
const properties =
[
{
label: "list-0",
rules: [{ required: true, message: 'name1 empty' }],
initialValue: 1,
type: 'Input',
props: {}
},
{
label: "list-1",
rules: [{ required: true, message: 'name1 empty' }],
initialValue: 2,
type: 'Input',
props: {}
},
{
label: "list-2",
rules: [{ required: true, message: 'name1 empty' }],
initialValue: 3,
type: 'Input',
props: {}
},
{
label: "list-3",
rules: [{ required: true, message: 'name1 empty' }],
initialValue: 4,
type: 'Input',
props: {}
},
]
const form = useFormStore();
const onSubmit = async (e) => {
e?.preventDefault?.();
const result = await form.validate();
console.log(result, 'result');
};
return (
<div style={{ padding: '0 8px' }}>
<RenderForm
form={form}
// formrender={formRenderStore}
properties={properties}
watch={watch} />
<div style={{ marginLeft: '120px' }}>
<Button onClick={onSubmit}>submit</Button>
</div>
</div>
);
}
Properties of the form rendering component:
properties
:{ [name: string]: FormNodeProps } | FormNodeProps[]
Rendering json data in the form of a DSL for a form.watch
:can listen to changes in the value of any field, for example:
const watch = {
'name1': (newValue, oldValue) => {
// console.log(newValue, oldValue)
},
'name2[0]': (newValue, oldValue) => {
// console.log(newValue, oldValue)
},
'name3': {
handler: (newValue, oldValue) => {
// console.log(newValue, oldValue)
}
immediate: true
}
...
<RenderForm watch={watch} />
}
components
:register other component for form to use.options
:GenerateFormNodeProps | ((params: GenerateFormNodeProps) => any)
Information about the parameters passed to the form node component. Lower priority than the form node's own parametersrenderList
: function that provides custom rendering List.renderItem
: function that provides custom render item.onPropertiesChange
:(newValue: ProertiesData) => void;
Callback function whenproperties
is changedformrender
: The form class responsible for rendering. Created withuseFormRenderStore()
.uneval
: Do not execute string expressions in the form.expressionImports
: External variables to be introduced in the string expression.
from react-easy-formcore
- 6.2.7 When the default component
RenderForm
reports an error in theform
tag in the nested case, you can settagName
to be replaced by another tag.
Only responsible for the rendering of the form
updateItemByPath
:(data?: any, path?: string, attributeName?: string) => void
Update the node corresponding to pathpath
, if updating specific attributes in the node thenattributeName
parameter is requiredsetItemByPath
:(data?: any, path?: string, attributeName?: string) => void
Set the node corresponding to pathpath
, orattributeName
if setting specific attributes in the nodeupdateNameByPath
:(newName?: string, path: string) => void
Update the name key of the specified pathdelItemByPath
:(path?: string, attributeName?: string) => void
Deletes the node corresponding to pathpath
, or theattributeName
parameter if the specific attribute in the node is deletedinsertItemByIndex
:(data: InsertItemType, index?: number, parent?: { path?: string, attributeName?: string }) => void
Add options based on the serial number and parent node pathgetItemByPath
:(path?: string, attributeName?: string) => void
Get the node corresponding to pathpath
, orattributeName
if it is a specific attribute in the nodemoveItemByPath
:(from: { parent?: string, index: number }, to: { parent?: string, index?: number })
Swap options in the tree from one location to anothersetProperties
:(data?: Partial<FormNodeProps>) => void
Setproperties
.
useFormRenderStore()
: createnew FormRenderStore()
by hook.useFormStore(defaultValues)
: createnew FormStore()
by hook.
Each item in the properties
property is a form node, and the nodes are divided into nested nodes and control nodes.
- Nested nodes:
Nodes with
properties
property describe which component the node is by thetype
andprops
fields, and do not carry form field components. - Control nodes:
Nodes without the
properties
property carry a form field component by default, providing some of the functionality of a form field. the default form field properties are inherited from theForm.Item
or theForm.List
component in react-easy-formcore.
// `name3` is Nested nodes,but not set component,`first` and `second` is Control nodes with form fields component。
const properties = {
name3: {
// type: '',
// props: {},
properties: {
first: {
label: 'first',
rules: [{ required: true, message: 'first empty' }],
type: 'Select',
props: {
style: { width: '100%' },
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
}
},
second: {
label: 'second',
rules: [{ required: true, message: 'second empty' }],
type: 'Select',
props: {
style: { width: '100%' },
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
}
}
}
},
}
- formNode type
// form component
export interface FormComponent {
type?: string;
props?: any & { children?: any | Array<FormComponent> };
hidden?: string | boolean;
}
export type UnionComponent<P> =
| React.ComponentType<P>
| React.ForwardRefExoticComponent<P>
| React.FC<P>
| keyof React.ReactHTML;
export type CustomUnionType = FormComponent | Array<FormComponent> | UnionComponent<any> | Function | ReactNode
// The type of nodes in the form tree
export interface FormNodeProps extends FormItemProps, FormComponent {
hidden?: string | boolean;
ignore?: boolean; // Mark the current field as a non-form field
inside?: CustomUnionType; // FormNode inner nested components
outside?: CustomUnionType; // FormNode outside nested components
readOnly?: boolean; // readonly?
readOnlyRender?: CustomUnionType | ReactNode; // form field's component render
typeRender?: any; // form field's component render
properties?: { [name: string]: FormNodeProps } | FormNodeProps[]; // Nested form components Nested objects when they are objects, or collections of arrays when they are array types
}
- The properties of the form node are set globally:
import RenderForm, { RenderFormChildren, useFormStore, Form } from "./form-render"
const properties = {
name3: {
label: "name3",
type: 'Input',
props: {}
},
}
const form = useFormStore();
// first way
<RenderForm
options={{
layout: 'vertical', // Attributes of a node
props: { disabled: true } // Properties of the component rendered by the 'type' field in the node
}}
/>
// second way was only sets the properties of the form field component (Form.Item).
// <Form form={form} layout="vertical">
// <RenderFormChildren
// properties={properties1}
// />
// </Form>
- Contextual information received by any component registered in the form:
export interface GeneratePrams<T = {}> {
name?: string; // Form fields of the node where the component is located
path?: string; // The rendering path of the node where the component is located
parent?: { name?: string; path?: string, field?: T & GenerateFormNodeProps; }; // Information about the parent node of the component
field?: T & GenerateFormNodeProps; // Information about the node where the component is located
formrender?: FormRenderStore;
form?: FormStore;
};
Forms are allowed to be nested, so they will involve finding a certain property. The paths follow certain rules
for Example:
a[0]
means the first option under the arraya
a.b
denotes theb
attribute of thea
objecta[0].b
means theb
attribute of the first option under the arraya
All property fields in form nodes except properties
can support string expressions for linkage
- Quick use: Computational expressions wrapping target property values with
{{
and}}
const properties = {
name1: {
label: 'name1',
valueProp: 'checked',
initialValue: true,
type: 'Checkbox',
props: {
children: 'option'
}
},
name2: {
label: "name2",
rules: '{{[{ required: formvalues && formvalues.name1 === true, message: "name2 is empty" }]}}',
initialValue: 1,
type: 'Input',
props: {}
},
}
// OR
const properties = {
name1: {
label: 'name1',
valueProp: 'checked',
initialValue: true,
type: 'Checkbox',
props: {
children: 'option'
}
},
name2: {
label: "name2",
rules: [{ required: '{{formvalues && formvalues.name1 === true}}', message: "name2 is empty" }],
initialValue: 1,
type: 'Input',
props: {}
},
}
- Rules for using string expressions
- A string has and can have only one pair of
{{
and}}
. - In addition to the three built-in variables (
form
(equaluseFormStore()
),formrender
(equaluseFormRenderStore()
),formvalues
(form value object)), external variables can be introduced viaexpressionImports
, and then referenced directly within the string expression and then refer to the variable name directly within the string expression. - Starting from 6.2.5, it is recommended to leave out the
$
symbol. It removed in 7.x versions.
import moment from 'moment'
import RenderForm from "./form-render"
const properties = {
name3: {
label: "name3",
initialValue: "{{moment().format('YYYY-MM-DD')}}",
type: 'Input',
props: {}
},
}
<RenderForm properties={properties} expressionImports={{ moment }} />