#Structured data
We also provide components for structured data input:
- ProFormList: Used for inputting structured multidimensional array data.
- ProFormFieldSet: Used for inputting structured one-dimensional array data.
- ProFormDependency: Components for managing data dependencies.
#ProFormList
ProFormList is basically the same as the Form.List API. It adds its own operation buttons: delete and copy, and it also includes a new row button.
<ProFormList
name="users"
initialValue={[
{
useMode: 'chapter',
},
]}
creatorButtonProps={{
position: 'top',
creatorButtonText: 'Building a line',
}}
creatorRecord={{
useMode: 'none',
}}
>
<ProFormSelect
key="useMode"
options={[
{
value: 'chapter',
label: 'Valid after stamping',
},
{
value: 'none',
label: 'Not effective',
},
]}
width="md"
name="useMode"
label="Contract agreement effective method"
/>
</ProFormList>#ProFormList API
| Parameters | Description | Type | Default Value |
|---|---|---|---|
| itemRender | custom Item, it can be used to place actions in other locations | (doms,listMeta)=> ReactNode | - |
| itemContainerRender | custom Item container, e.g. wrap each item in a Card | (doms,listMeta)=> ReactNode | - |
| creatorRecord | Default value of a new line | Record<string, any> | () => Record<string, any> | - |
| creatorButtonProps | Configuration of a new line of buttons, set false to hide it | false | (ButtonProps & { creatorButtonText?: ReactNode, position?: "top"|"bottom" }) | {creatorButtonText:"Create a new line"} |
| label | Same as From.Item | ReactNode | - |
| tooltip | Tooltip info next to the title | LabelTooltipType | - |
| name | The value of list in the form, required | NamePath | - |
| alwaysShowItemLabel | always show label in Item | boolean | - |
| actionRef | The built-in operation of the current List, you can add, delete, modify and check list items | MutableRefObject<{add,remove,move,get,getList} | undefined> | - |
| actionGuard | FormItem's interceptor, including deletion and addition interception, you can use actionRef to get the value of the current row | {beforeAddRow:(defaultValue,index,count)=>boolean|Promise<boolean>, beforeRemoveRow:(index,count)=>boolean|Promise<boolean>} | - |
| min | The minimum number of entries, if the current data entry is less than this number, it cannot be deleted | number | - |
| max | The maximum number of entries, when adding or copying, if the current data entry exceeds this number, it cannot be added or copied | number | - |
| copyIconProps | Copy button configuration, false to cancel | { Icon?: React.FC<any>; tooltipText?: string; } | false | - |
| deleteIconProps | Delete button configuration, false can cancel | { Icon?: React.FC<any>; tooltipText?: string; } | false | - |
| actionRender | custom action button | (field,action,defaultActionDom,count)=>React.ReactNode[] | - |
| fieldExtraRender | Append a dom at the end, e.g. a custom create button | (fieldAction,meta)=> ReactNode | - |
| onAfterAdd | Hook after adding data | (defaultValue: StoreValue, insertIndex: number, count: number) => void | - |
| onAfterRemove | Hook after deleting data | (index: number, count: number) => void | - |
| arrowSort | Whether to enable arrow button sorting | boolean | - |
| upIconProps | Sort-up button configuration, false to cancel | { Icon?: React.FC<any>; tooltipText?: string; } | false | - |
| downIconProps | Sort-down button configuration, false to cancel | { Icon?: React.FC<any>; tooltipText?: string; } | false | - |
| transform | Convert value on submit, generally used to transform values into submitted data | (value, namePath, allValues) => any | - |
| isValidateList | Whether to validate the list is empty at the same time | boolean | false |
| emptyListMessage | Empty list message shown when isValidateList is true | string | - |
| readonly | Whether read-only mode | boolean | - |
| required | Whether required (used with rules to show validation message) | boolean | - |
#actionRef Action item instance
const actionRef = useRef<
FormListActionType<{
name: string;
}>
>();
return (
/**
* @name gets the list operation instance
* @description can delete, add, move and other operations
*
* @example actionRef?.current.add?.({},1);
* @example actionRef?.current.remove?.(1);
* @example actionRef?.current.move?.(1,2);
* @example actionRef?.current.get?.(1);
*/
<>
<Space>
<Button
type="primary"
onClick={() => {
const list = actionRef.current?.getList();
actionRef.current?.add({
name: 'New' + list?.length,
});
}}
>
add a line
</Button>
<Button
danger
onClick={() => {
actionRef.current?.remove(1);
}}
>
delete a line
</Button>
<Button
onClick={() => {
actionRef.current?.move(1, 0);
}}
>
move to the first line
</Button>
<Button
type="dashed"
onClick={() => {
const row = actionRef.current?.get(1);
console.log(row);
}}
>
Get a row of data
</Button>
<Button
type="dashed"
onClick={() => {
const row = actionRef.current?.getList();
console.log(row);
}}
>
get all data
</Button>
</Space>
<ProFormList actionGuard={actionGuard} actionRef={actionRef}>
<ProFormText key="useMode" name="name" label="name" />
</ProFormList>
</>
);#actionGuard interceptor
actionGuard can intercept list operations, and now there are two events beforeAddRow and beforeRemoveRow.
const actionRef = useRef<
FormListActionType<{
name: string;
}>
>();
const actionGuard = {
beforeAddRow: async (defaultValue, insertIndex, count) => {
return new Promise((resolve) => {
console.log(defaultValue?.name, insertIndex, count);
setTimeout(() => resolve(true), 1000);
});
},
beforeRemoveRow: async (index, count) => {
const row = actionRef.current?.get(index as number);
console.log('--->', index, count, row);
return new Promise((resolve) => {
if (index === 0) {
resolve(false);
return;
}
setTimeout(() => resolve(true), 1000);
});
},
};
return (
<ProFormList actionGuard={actionGuard} actionRef={actionRef}>
<ProFormText key="useMode" name="name" label="name" />
</ProFormList>
);#actionRender custom action button
/**
* @name custom action button
*
* @example delete button
* actionRender:(field,action)=><a onClick={()=>action.remove(field.name)}>remove</a>
* @example can only add up to three new lines
* actionRender:(f,action,_,count)=><a onClick={()=>
* count>2?alert("Up to three lines!"):action.add({id:"xx"})}>delete
* </a>
*/
actionRender?: (
field: FormListFieldData,
/**
* Operational ability
* @example action.add(data) add a new line
* @example action.remove(index) delete a line
* @example action.move(formIndex,targetIndex) move a row
*/
action: FormListOperation,
/**
* Default operation dom
* [copy, delete]
*/
defaultActionDom: ReactNode[],
/**
* There are currently several list items
*/
count: number,
) => ReactNode[];
#ProFormList RenderProps mode
ProFormList supports passing in a method to get the information and shortcut operations of the current row, which is very convenient for complex linkage.
<ProFormList>
{(
// Basic information of the current row {name: number; key: number}
meta,
// current line number
index,
/**
* action
* @name some shortcut methods for manipulating rows
* @example add data to the second line action.add?.({},1);
* @example delete the second line action.remove?.(1);
* @example moved from 1 to 2: action.move?.(2,1);
* @example Get the data of the current row: action.getCurrentRowData() -> {id:"xxx",name:'123',age:18}
* @example Set current row data: {id:"123",name:'123'} -> action.setCurrentRowData({name:'xxx'}) -> {id:"123",name:'xxx' }
* @example clear the data of the current row: {id:"123",name:'123'} -> action.setCurrentRowData({name:undefined}) -> {id:"123"}
*/
action,
// total number of rows
count,
) => {
return (
<div key="row">
<ProFormText name="id" />
<ProFormText name="name" />
</div>
);
}}
</ProFormList>The types of these four parameters are defined as follows:
type RenderActionParams = {
/**
* @name The meta information of the current line
* @example {name: number; key: number}
*/
meta: FormListFieldData;
/**
* @name the line number of the current line
*/
index: number;
/**
* @name some shortcut methods for manipulating rows
* @example add data to the second line action.add?.({},1);
* @example delete the second line action.remove?.(1);
* @example moved from 1 to 2: action.move?.(2,1);
* @example Get the data of the current row: action.getCurrentRowData() -> {id:"xxx",name:'123',age:18}
* @example Set current row data: {id:"123",name:'123'} -> action.setCurrentRowData({name:'xxx'}) -> {id:"123",name:'xxx' }
* @example clear the data of the current row: {id:"123",name:'123'} -> action.setCurrentRowData({name:undefined}) -> {id:"123"}
*/
action: FormListOperation & {
/**
* @name Get the data of the current row
* @example getCurrentRowData -> {id:"xxx",name:'123',age:18}
*/
getCurrentRowData: () => any;
/**
* @name sets the data of the current row
* @example {id:"123",name:'123'} -> setCurrentRowData({name:'xxx'}) -> {id:"123",name:'123'}
* @example {id:"123",name:'123'} -> setCurrentRowData({name:undefined}) -> {id:"123"}
*/
setCurrentRowData: (data: any) => void;
};
/**
* @name the total number of rows
*/
count: number;
};#ProFormFieldSet
ProFormFieldSet can combine the values of multiple children inside and store them in ProForm, and can be transformed at commit time via transform. Here is a simple usage to easily combine multiple input fields and format them to the desired data.
<ProFormFieldSet
name="list"
label="List of components"
// Two methods are supported, type="group" will be wrapped with input.group
// If not configured, use space by default
type="group"
transform={(value: any) => ({ startTime: value[0], endTime: value[1] })}
>
<ProFormText width="md" />
<ProFormText width="md" />
<ProFormText width="md" />
</ProFormFieldSet#ProFormDependency
ProFormDependency is a simplified version of Form.Item. It comes with noStyle and shouldUpdate built-in by default. You only need to configure the name parameter to specify which data to depend on. ProFormDependency will automatically handle differences and extract the corresponding values from the form.
The name parameter must be an array. For nested structures, you can configure it like this: name={['name', ['name2', 'text']]}. The values of the configured name will be passed into renderProps. For example, if name={['name', ['name2', 'text']]}, the values passed will be { name: string, name2: { text: string } }.
<ProFormDependency name={['name']}>
{({ name }) => {
return (
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping.',
},
]}
width="md"
name="useMode"
label={`Effective as per the agreement in the contract with《${name}》`}
/>
);
}}
</ProFormDependency>#Code examples
#Custom tooltip for delete and copy
import { CloseCircleOutlined, SmileOutlined } from '@ant-design/icons';
import {
ProForm,
ProFormGroup,
ProFormList,
ProFormText,
} from '@ant-design/pro-components';
import { Segmented } from 'antd';
import { useState } from 'react';
const Demo = () => {
const [readonly, setReadonly] = useState(false);
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 16,
}}
>
<Segmented
block
options={[
{
label: '编辑',
title: '编辑',
value: 'edit',
},
{
label: '只读',
title: '只读',
value: 'readonly',
},
]}
onChange={(e) => {
setReadonly(e === 'readonly');
}}
/>
<ProForm
name="list-tooltip-demo"
readonly={readonly}
onFinish={async (e) => console.log(e)}
>
<ProFormText name="name" label="姓名" />
<ProFormList
name="labels"
label="用户信息"
initialValue={[
{
value: '333',
label: '333',
},
]}
copyIconProps={{ Icon: SmileOutlined, tooltipText: '复制此项到末尾' }}
deleteIconProps={{
Icon: CloseCircleOutlined,
tooltipText: '不需要这行了',
}}
>
<ProFormGroup key="group">
<ProFormText name="value" label="值" />
<ProFormText name="label" label="显示名称" />
</ProFormGroup>
</ProFormList>
</ProForm>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#FormList with dependent fields
import {
ProForm,
ProFormDependency,
ProFormList,
ProFormText,
} from '@ant-design/pro-components';
import { Button } from 'antd';
const Demo = () => {
return (
<ProForm name="group-base-use-demo">
<ProFormList
name={['default', 'users']}
label="用户信息"
initialValue={[
{
name: '1111',
},
]}
itemContainerRender={(doms) => {
return <ProForm.Group>{doms}</ProForm.Group>;
}}
alwaysShowItemLabel
>
{(f, index, action) => {
return (
<>
<ProFormText
initialValue={index}
name="rowKey"
label={`第 ${index} 配置`}
/>
<ProFormText name="name" key="name" label="姓名" />
<ProFormDependency key="remark" name={['name']}>
{({ name }) => {
if (!name) {
return (
<span
style={{
lineHeight: '92px',
}}
>
输入姓名展示
</span>
);
}
return <ProFormText name="remark" label="昵称详情" />;
}}
</ProFormDependency>
<div
style={{
display: 'flex',
alignItems: 'flex-end',
gap: '8px',
height: 60,
}}
>
<Button
type="primary"
key="SET"
onClick={() => {
action.setCurrentRowData({
name: 'New Name' + index,
remark: 'New Remark' + index,
});
}}
>
设置此项
</Button>
<Button
type="dashed"
key="clear"
onClick={() => {
action.setCurrentRowData({
name: undefined,
remark: undefined,
});
}}
>
清空此项
</Button>
</div>
</>
);
}}
</ProFormList>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Adjustable position for the create button
import {
ProForm,
ProFormDatePicker,
ProFormDigit,
ProFormFieldSet,
ProFormGroup,
ProFormList,
ProFormRadio,
ProFormSelect,
ProFormText,
} from '@ant-design/pro-components';
import { ConfigProvider } from 'antd';
import { useState } from 'react';
const Demo = () => {
const [position, setPosition] = useState<'bottom' | 'top'>('bottom');
return (
<ConfigProvider componentSize="small">
<ProFormRadio.Group
fieldProps={{
value: position,
onChange: (e) => setPosition(e.target.value),
}}
options={[
{
label: '顶部',
value: 'top',
},
{
label: '底部',
value: 'bottom',
},
]}
/>
<ProForm
name="group-list-demo"
onFinish={async () => {
}}
>
<ProFormText width="sm" name="id" label="主合同编号" />
<ProFormText
name="project"
width="md"
label="项目名称"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
label="商务经理"
initialValue="书琰"
/>
<ProFormList
name="users"
label="用户信息"
rules={[
{
required: true,
validator: async (_, value) => {
if (value && value.length > 0) {
return;
}
throw new Error('至少要有一项!');
},
},
]}
creatorButtonProps={{
position,
}}
creatorRecord={{
name: '',
}}
initialValue={[
{
name: '书琰',
nickName: '琰琰',
age: 28,
birth: '2024-01-15',
sex: 'man',
addrList: [{ addr: ['taiyuan', 'changfeng'] }],
},
]}
>
<ProFormGroup key="group">
<ProFormText
rules={[
{
required: true,
},
]}
name="name"
label="姓名"
/>
<ProFormDigit name="age" label="年龄" width="sm" />
<ProFormSelect
label="性别"
name="sex"
width="xs"
valueEnum={{
man: '男性',
woman: '女性',
}}
/>
<ProFormDatePicker name="birth" label="出生日期" />
<ProFormFieldSet name="addr" label="地址">
<ProFormSelect
valueEnum={{
taiyuan: '山西',
hangzhou: '杭州',
}}
/>
<ProFormSelect
valueEnum={{
changfeng: '长风街',
gongzhuan: '工专路',
}}
/>
</ProFormFieldSet>
</ProFormGroup>
</ProFormList>
</ProForm>
</ConfigProvider>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Forms nested within each other
import {
ProCard,
ProForm,
ProFormGroup,
ProFormList,
ProFormText,
} from '@ant-design/pro-components';
const Demo = () => {
return (
<ProForm name="nested-list-demo" onFinish={async (e) => console.log(e)}>
<ProFormText name="name" label="姓名" />
<ProFormList
name="users"
label="用户信息"
initialValue={[
{
name: '1111',
},
]}
itemRender={({ listDom, action }, { record }) => {
return (
<ProCard
variant="outlined"
extra={action}
title={record?.name}
style={{
marginBlockEnd: 8,
}}
>
{listDom}
</ProCard>
);
}}
>
<ProFormGroup>
<ProFormText name="name" label="姓名" />
<ProFormText name="nickName" label="昵称" />
</ProFormGroup>
<ProFormList
name="labels"
label="用户信息"
initialValue={[
{
value: '333',
label: '333',
},
]}
copyIconProps={{
tooltipText: '复制此项到末尾',
}}
deleteIconProps={{
tooltipText: '不需要这行了',
}}
>
<ProFormGroup key="group">
<ProFormText name="value" label="值" />
<ProFormText name="label" label="显示名称" />
</ProFormGroup>
</ProFormList>
</ProFormList>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Complex field dependencies
import {
ProForm,
ProFormDependency,
ProFormList,
ProFormSelect,
ProFormText,
} from '@ant-design/pro-components';
const Demo = () => {
return (
<ProForm name="group-dependency-demo">
<ProFormList
name={['default', 'users']}
label="用户信息"
initialValue={[
{
name: '我是姓名',
},
]}
itemContainerRender={(doms) => {
return <ProForm.Group>{doms}</ProForm.Group>;
}}
>
{(f, index, _action) => {
return (
<>
<ProFormText
initialValue={index}
name="rowKey"
label={`第 ${index} 配置`}
/>
<ProFormText name="name" label="姓名" />
<ProFormDependency name={['name']}>
{({ name }) => {
if (!name) {
return (
<span
style={{
lineHeight: '32px',
}}
>
输入姓名展示
</span>
);
}
return <ProFormText name="remark" label="昵称详情" />;
}}
</ProFormDependency>
<ProFormSelect
name="addr"
width="md"
label="与 name 联动的选择器"
dependencies={['name']}
request={async (params) => [
{ label: params.name, value: 'all' },
{ label: 'Unresolved', value: 'open' },
{ label: 'Resolved', value: 'closed' },
{ label: 'Resolving', value: 'processing' },
]}
/>
</>
);
}}
</ProFormList>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Behavior guard
import type { FormListActionType } from '@ant-design/pro-components';
import {
ProCard,
ProForm,
ProFormGroup,
ProFormList,
ProFormText,
} from '@ant-design/pro-components';
import { Button, Space, message } from 'antd';
import { useRef } from 'react';
const Demo = () => {
const actionRef = useRef<
FormListActionType<{
name: string;
}>
>();
return (
<>
<Space
style={{
marginBlockEnd: 24,
}}
>
<Button
type="primary"
onClick={() => {
const list = actionRef.current?.getList();
actionRef.current?.add({
name: '新增' + list?.length,
});
}}
>
增加一行
</Button>
<Button
danger
onClick={() => {
actionRef.current?.remove(1);
}}
>
删除一行
</Button>
<Button
onClick={() => {
actionRef.current?.move(1, 0);
}}
>
移动到第一行
</Button>
<Button
type="dashed"
onClick={() => {
const row = actionRef.current?.get(1);
message.info(JSON.stringify(row ?? null));
}}
>
获取一行数据
</Button>
<Button
type="dashed"
onClick={() => {
const row = actionRef.current?.getList();
message.info(JSON.stringify(row ?? []));
}}
>
获取所有数据
</Button>
</Space>
<ProForm name="pro-form-list-demo" onFinish={async (e) => console.log(e)}>
<ProFormList
name="users"
label="用户信息"
initialValue={[
{
name: '1111',
},
]}
creatorRecord={{
name: '222',
}}
actionGuard={{
beforeAddRow: async (_defaultValue, _insertIndex) => {
return new Promise((resolve) => {
setTimeout(() => resolve(true), 1000);
});
},
beforeRemoveRow: async (index) => {
return new Promise((resolve) => {
if (index === 0) {
message.error('这行不能删');
resolve(false);
return;
}
setTimeout(() => resolve(true), 1000);
});
},
}}
itemRender={({ listDom, action }, { record }) => {
return (
<ProCard
variant="outlined"
extra={action}
title={record?.name}
style={{
marginBlockEnd: 8,
}}
>
{listDom}
</ProCard>
);
}}
actionRef={actionRef}
>
<ProFormGroup key="group">
<ProFormText name="name" label="姓名" />
<ProFormText name="age" label="年龄" />
</ProFormGroup>
</ProFormList>
</ProForm>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);