SchemaForm - JSON Form
Generates forms through a declarative schema configuration. The schema structure closely resembles Antd Table's columns.
Basic Concepts
A basic Schema form:
Before diving deeper, let's clarify several key concepts.
SchemaForm is built upon the Antd Form component. It generates form items by iterating through a schema array.
In this example, each object in the columns array represents a schema.
{
title: 'Username',
dataIndex: 'username',
fieldProps: {
placeholder: 'Please enter username'
},
formItemProps: {
rules: [
{
required: true,
},
],
},
},
If written using Antd Form directly, this schema would generate:
import { Form, Input } from 'antd';
<Form>
<Form.Item
label="Username"
name="username"
rules={[
{
required: true,
},
]}
>
<Input placeholder="Please enter username" />
</Form.Item>
</Form>;
It's clear that in the schema:
titlecorresponds to Form.Item'slabel.dataIndexcorresponds to Form.Item'sname.formItemPropsvalues are passed to the Form.Item component.fieldPropsvalues are passed to the component wrapped by Form.Item (Input in this example).
Let's look at the second schema:
{
title: "Gender",
dataIndex: "gender",
valueType: "radio",
fieldProps: {
options: [
{ label: "Male", value: "male" },
{ label: "Female", value: "female" },
{ label: "Other", value: "other" },
],
},
};
The difference from the first schema is the additional valueType field. Obviously, SchemaForm renders different components based on the valueType field. In this example, valueType: 'radio' corresponds to a Radio.Group component, with options passed through fieldProps.
This is one of the advantages of using SchemaForm - most commonly used components can be rendered through valueType.
The schema part is now covered. Let's look at the FormInstance.
The FormInstance is an important concept in Antd Form. Through the methods in the instance, we can perform many operations, such as: submitting forms, resetting forms, assigning values to forms, etc.
In SchemaForm, we get the form instance through the formRef property.
import { useRef } from 'react';
import { ProFormInstance, SchemaForm } from 'react-admin-kit';
const formRef = useRef<ProFormInstance>() // Note: The type definition used here is ProFormInstance. It extends some methods based on antd form instance
<SchemaForm
formRef={formRef}
>
formRef.current?.submit() // Submit
formRef.current?.resetFields() // Reset
formRef.current?.setFieldsValue() // Set values
Other methods in the form instance will be introduced in detail later.
These are the basic concepts of SchemaForm. After reading this, you can already start writing some simple forms ✨.
As you continue to use it, you can keep checking the various examples below to learn about other properties of SchemaForm. I believe you will become more and more proficient.
valueType
The valueType field is crucial in schema configuration, determining which form component gets rendered. Common types include money, digit, date, dateRange, select, radio, textarea, etc. (Complete list here). When unspecified, defaults to Input.
For custom components, use renderFormItem. Custom components must follow Form.Item conventions (accept value/onChange props).
{
renderFormItem: (schema, config, form) => {
return <MyComp />;
};
}
required
Since required fields are common, SchemaForm provides a required shorthand for formItemProps: { rules: [{ required: true }]}. formItemProps takes precedence.
Form Layouts
Traditional Layouts
Grid Mode
Grid mode utilizes Ant Design's Grid System, controlling layout through Row and Col props.
<SchemaForm
grid
rowProps={{}}
colProps={{}}
columns={[
{
title: 'name',
colProps: {}, // Higher priority
},
{
title: 'age',
colProps: {}, // Higher priority
},
]}
/>
When grid is enabled, it renders as:
// pseudocode for illustration only
<Form>
<Row {...rowProps}>
<Col {...colProps}>
<Form.Item>
<Field />
<Form.Item>
</Col>
<Col {...colProps}>
<Form.Item>
<Field />
<Form.Item>
</Col>
</Row>
</Form>
Grid system enables flexible layouts:
Horizontal Grid Alignment
In horizontal grid mode, label widths may vary:
Solutions include setting minimum widths:
Or specifying individual widths:
labelColis an Antd Form prop that accepts Col props like{span: 8}or{flex: '0 0 30%'}.Values are calculated relative to the form item's width (divided into 24 units).
{span: 8}means the label occupies 8/24 of the item's width.
Placeholder Spacing
Use empty placeholders for forced line breaks.
Form Operations
Readonly Mode
Enable via readonly prop or schema's readonly. Key notes:
- Use
render(notrenderFormItem) for custom readonly displays - Return null from render for empty space:
render: () => null
💡 Rakjs extends the readonly render function's second parameter (record) to include all form values.
Readonly Table Mode
Readonly mode can also be transformed into a table-like format, similar to the Antd Descriptions component.
Field Dependencies
Initial Values and Value Assignment
-
[Show User] defaults to No
-
Toggles to Yes when clicking "Set Values"
-
Resets to No when clicking "Reset"
Submit Button
The submitter prop defaults to false. When enabled, automatically generates submit buttons.
Customize button props:
Readonly/Editable Toggle
Practical scenarios:
1. Form Display Simulation
2. Dependent Field Display in Readonly Mode
Form Data
Value Retrieval
SchemaForm provides multiple value retrieval methods. The most common is onFinish.
Other methods via form instance:
| Form Instance Method | Description | Source |
|---|---|---|
getFieldsValue | Get form values | Antd Form |
validateFields | Return form values after validation passes | Antd Form |
getFieldsFormatValue | Get form values (will additionally process time format according to valueType; will also process transform in schema) | ProComponent |
onFinish | Same as above | ProComponent |
validateFieldsReturnFormatValue | Return form values after validation passes (with additional transformation as above | ProComponent |
These methods have subtle differences in handling formats and transformations.
Rule of thumb: Use the first two methods when you need raw values, others for processed values.
| Form Instance Method | Result |
|---|---|
getFieldsValue | {
"tags": [
"tag01",
"tag02"
],
"validDate": [
"dayjs object",
"dayjs object"
]
} |
validateFields | "Same as above" |
getFieldsFormatValue | {
"tagIds": "tag01,tag02",
"validDate": [
"2024-10-01",
"2024-10-07"
]
} |
onFinish | "Same as above" |
validateFieldsReturnFormatValue | "Same as above" |
ConvertValue and Transform
Backend data often requires transformation before being used in form controls. (ConvertValue)
Similarly, form-collected data may need processing before being sent to the server. (Transform)
This is particularly common in scenarios like file attachments upload, where:
// there is a upload component
{
title: 'Files',
dataIndex: 'fileList',
renderFormItem: () => <FormUpload />
}
// <FormUpload /> expects: [{ name: 'FileA', url: 'www.xx.com/xx' }]
// Backend returns for display: [{ id: 1, fileName: 'FileA', filePath: 'www.xx.com/xx' }]
// Submission requires: fileIds: '1,2'
convertValue and transform handle these scenarios.
For file uploads, use the FormUpload component.
Convention Handling
For components like Select and TreeSelect, when the labelInValue property is enabled, the component no longer accepts strings but requires an object { label: string, value: string } for display purposes.
As a result, the frontend needs to transform this data, and when submitting, the object must be split back into its original fields.
// Example: A user dropdown component with labelInValue enabled
{
title: 'User',
dataIndex: 'user',
valueType: 'select',
fieldProps: {
labelInValue: true,
options: [
{ value: 1, label: 'jack' },
{ value: 2, label: 'tom' },
]
}
}
// Backend returns userId: 1, userName: 'jack' for display
// => Frontend transforms into user: { value: 1, label: 'jack' }
// => On submission, the object is converted back to userId: 2, userName: 'tom'
Since this scenario is quite common, RAK aims to simplify the process by introducing a convention for dataIndex, as follows:
- 👉 If dataIndex contains a comma
,, RAK will automatically assemble an object based on the fields before and after the comma. During submission, the object will be split back. The field before the comma maps to value, and the field after maps to label.
// Example: A user dropdown component with labelInValue enabled
{
title: 'User',
dataIndex: 'userId,userName',
valueType: 'select',
fieldProps: {
labelInValue: true,
options: [
{ value: 1, label: 'jack' },
{ value: 2, label: 'tom' },
]
}
}
// With this convention, simply provide the values like this:
initialValues={{ userId: 1, userName: 'jack' }}
// Or:
formRef.current?.setFieldsValue({ userId: 1, userName: 'jack' })
// Upon submission, the form will NOT receive:
`{'userId,userName': { value: 1, label: 'jack' }}`
// Instead, it will receive:
{
userId: 1,
userName: 'jack'
}
- If the component expects object keys other than
valueandlabel, you can customize them using an underscore. For example,userId,userName_id,namesplits the underscore, where the fields after the underscore are also comma-separated. In this case, when assigning{userId: 1, userName: 'jack'}to the component, the value will be transformed into{id: 1, name: 'jack' }` before being passed to the component.
Storing Additional Data with innerRef
innerRef is a utility ref containing helpful methods for special scenarios.
Use innerRef.current?.setData() to store extra data, consumable by other fields.
Similar to React's setState, setData merges updates.
For Embed Mode, pass innerRef to ProForm:
import { ProForm } from 'react-admin-kit';
import { useRef } from 'react'
const innerRef = useRef();
...
<ProForm
innerRef={innerRef}
...
>
...
</>
Advanced Layouts
Embed Mode
For complex forms, using embed mode allows each section to have its own layout configuration while collecting data in separate objects through the valueBaseName property.
import { ProForm, SchemaForm } from 'react-admin-kit';
import { Card } from 'antd';
<ProForm>
<SchemaForm embed valueBaseName="one" />
<div>
<SchemaForm embed valueBaseName="two" />
</div>
<Card>
<SchemaForm embed valueBaseName="three" />
</Card>
</ProForm>;
// The collected form values upon submission will be: { one: ..., two: ..., three: ... }`
👉 Important note: In embed mode, the
valueBaseNameimplementation simply converts the schema's dataIndex into an array format. See this Ant Design example.Therefore, when using setFieldsValue, you must include the valueBaseName in the value structure.
setFieldsValue({ business: { company: 'xxx' } });For dependency controls, when valueType='dependency' and
valueBaseNamehas a value, thenameproperty should use nested array syntax.{ valueType: 'dependency', name: [['business', 'serviceName']] } 👈
Group Layout
When valueType is set to group, it enables grouped layout mode. Each group functions as a distinct section, with form items generated from the contents of columns.
By default, these form items are wrapped in Ant Design's Space component. Therefore, you can pass Space component's API properties through fieldProps.
Group Layout grid
The group layout operates at two levels:
-
Outer layer (section title)
-
Inner layer (form items within columns)
Accordingly, colProps configurations must be specified separately for each level.
Form Array
When valueType is set to formList, it generates a form array, which is particularly useful for collecting array-type data.
For example, the following demo allows collecting multiple shop infomation:
formList is essentially a component, and its API can be configured via fieldProps.
Grid Layout
The form items must be wrapped in valueType='group'.
Custom Styling
You can further customize the styling and action buttons using the itemRender property. See the example for usage.
The
itemRenderfunction signature:({ listDom, action }, options) => ReactNodeThe
optionssignature:{name, field, index, record, fields, operations, meta}
API
SchemaForm
SchemaForm type is a combination of SchemaFormSelfType and SchemaFormOriginType.
SchemaFormSelfType
| Property | Description | Type | Default |
|---|---|---|---|
| embed | Whether it is embed mode | boolean | false |
| valueBaseName | When embed is enabled, handle nested data structure; when collecting data in onFinish, it will be attached under this field. Only applicable in embed mode. | string | false |
| readonly | Whether it is readonly mode | boolean | false |
| readonlyType | Display type when readonly. Options: form or descriptions | "form" | "descriptions" | form |
| columns | Configuration description of form items; | Required | |
| onFinish | Callback when the form is submitted; | (values) => Promise | void | -- |
| formRef | Used to get the form instance; please use formRef instead of passing a form instance through the form property. Because RAK components have extra encapsulation for the form instance, you must get it through formRef. | RefObject<ProFormInstance> | -- |
| innerRef | RAK specific ref, used to store some utility functions and data. | -- | |
| submitter | Submit button related configuration. | boolean | SubmitterProps & { style: React.CSSProperties } | false |
| descriptionsProps | Table style configuration in descriptions mode | Omit<DescriptionsProps, 'items' | 'columns'> | -- |
SchemaFormOriginType
| Property | Description | Type | Default |
|---|---|---|---|
| form | -- | FormInstance<Record<string, any>> | -- |
| name | -- | string | -- |
| initialValues | -- | Store | -- |
| component | -- | string | false | FC<any> | ComponentClass<any, any> | -- |
| fields | -- | FieldData<any>[] | -- |
| validateMessages | -- | ValidateMessages | -- |
| onValuesChange | -- | ((changedValues: any, values: Record<string, any>) => void) | -- |
| onFieldsChange | -- | ((changedFields: FieldData<any>[], allFields: FieldData<any>[]) => void) | -- |
| onFinishFailed | -- | ((errorInfo: ValidateErrorEntity<Record<string, any>>) => void) | -- |
| validateTrigger | -- | string | false | string[] | -- |
| preserve | -- | boolean | -- |
| clearOnDestroy | -- | boolean | -- |
| prefixCls | -- | string | -- |
| colon | -- | boolean | -- |
| layout | -- | FormLayout | -- |
| labelAlign | -- | FormLabelAlign | -- |
| labelWrap | -- | boolean | -- |
| labelCol | -- | ColProps | -- |
| wrapperCol | -- | ColProps | -- |
| feedbackIcons | -- | FeedbackIcons | -- |
| size | -- | SizeType | -- |
| disabled | -- | boolean | -- |
| scrollToFirstError | -- | boolean | ScrollFocusOptions | -- |
| requiredMark | -- | RequiredMark | -- |
| hideRequiredMark | Will warning in future branch. Pls use `requiredMark` instead. | boolean | -- |
| rootClassName | -- | string | -- |
| variant | -- | "outlined" | "borderless" | "filled" | "underlined" | -- |
| loading | 表单按钮的 loading 状态 | boolean | -- |
| onLoadingChange | 这是一个可选的属性(onLoadingChange),它接受一个名为loading的参数,类型为boolean,表示加载状态是否改变。 | ((loading: boolean) => void) | -- |
| formRef | 获取 ProFormInstance | MutableRefObject<any> | RefObject<any> | -- |
| syncToUrl | 同步结果到 url 中 | boolean | ((values: Record<string, any>, type: "get" | "set") => Record<string, any>) | -- |
| syncToUrlAsImportant | 当 syncToUrl 为 true,在页面回显示时,以url上的参数为主,默认为false | boolean | -- |
| extraUrlParams | 额外的 url 参数 中 | Record<string, any> | -- |
| syncToInitialValues | 同步结果到 initialValues,默认为true如果为false,reset的时将会忽略从url上获取的数据 | boolean | -- |
| omitNil | 如果为 false,会原样保存。 | boolean | true |
| dateFormatter | 格式化 Date 的方式,默认转化为 string | false | "string" | "number" | (string & {}) | ((value: Dayjs, valueType: string) => string | number) | -- |
| onInit | 表单初始化成功,比如布局,label等计算完成 | ((values: Record<string, any>, form: ProFormInstance<any>) => void) | -- |
| params | 发起网络请求的参数 | Record<string, any> | -- |
| request | 发起网络请求的参数,返回值会覆盖给 initialValues | ProRequestData<Record<string, any>, Record<string, any>> | -- |
| isKeyPressSubmit | 是否回车提交 | boolean | -- |
| formKey | 用于控制form 是否相同的key,高阶用法 | string | -- |
| autoFocusFirstInput | -- | boolean | -- |
| readonly | 是否只读模式,对所有表单项生效 | boolean | -- |
| grid | open grid layout | boolean | false |
| colProps | only works when grid is enabled | ColProps | { xs: 24 } |
| rowProps | only works when grid is enabled | RowProps | { gutter: 8 } |
SchemaFormInnerRefType
| Parameter | Description | Type | Default |
|---|---|---|---|
| data | Can be used to store extra data in the form | Record<string, any> | {} |
| setData | Store data; setData works like React's setState, you only need to pass the fields you care about, and it will not overwrite other fields. | (Record<string, any>) => void | -- |
FormColumnType
| Property | Description | Type | Default |
|---|---|---|---|
| fieldProps | Inject innerRef into the fieldProps method | object | ((form: ProFormInstance, innerRef: BaseInnerRef, config: any) => object) | -- |
| renderFormItem | Inject innerRef into the renderFormItem method | ((item: any, config: any, form: any, innerRef: BaseInnerRef) => any) | -- |
| columns | Redefine the columns type | FormColumnType<any, "text">[] | ((values: any) => FormColumnType<any, "text">[]) | -- |
| required | Whether it is required; shorthand for formItemProps: { rules: [{ required: true }] } | boolean | -- |
| dataIndex | Can use conventional automatic value processing: userId, userName or userId, userName_id, name; | string | -- |
| name | -- | any | -- |
| className | -- | string | -- |
| title | 支持 ReactNode 和 方法 | ReactNode | ((schema: ProSchema<any, { tooltip?: ReactNode; key?: Key; className?: string | undefined; width?: string | number | undefined; name?: any; defaultKeyWords?: string | undefined; } & Pick<...> & { ...; }, ProSchemaComponentTypes, FormFieldType | "text", unknown>, type: ProSchemaComponentTypes,... | -- |
| params | 从服务器请求的参数,改变了会触发 reload | Record<string, any> | ((record: any, column: ProSchema<any, { tooltip?: ReactNode; key?: Key; className?: string | undefined; width?: string | number | undefined; name?: any; defaultKeyWords?: string | undefined; } & Pick<...> & { ...; }, "form", "text", unknown>) => Record<...>) | undefined | -- |
| request | 从服务器请求枚举 | ProFieldRequestData<any> | -- |
| readonly | 是否只读模式 | boolean | -- |
| colProps | only works when grid is enabled | ColProps | { xs: 24 } |
| rowProps | only works when grid is enabled | RowProps | { gutter: 8 } |
| key | 确定这个列的唯一值,一般用于 dataIndex 重复的情况 | Key | -- |
| tip | -- | string | -- |
| tooltip | 展示一个 icon,hover 是展示一些提示信息 | ((string | number | boolean | (TooltipPropsWithTitle & { icon?: ReactElement<any, string | JSXElementConstructor<any>>; }) | (TooltipPropsWithOverlay & { ...; }) | ReactElement<...> | Iterable<...> | ReactPortal) & (string | ... 4 more ... | ReactPortal)) | null | undefined | -- |
| valueEnum | 支持 object 和Map,Map 是支持其他基础类型作为 key | ProSchemaValueEnumObj | ProSchemaValueEnumMap | ((row: any) => ProSchemaValueEnumObj | ProSchemaValueEnumMap) | -- |
| formItemProps | 自定义的 formItemProps | FormItemProps<any> | ((form: FormInstance<any>, config: { key?: Key; dataIndex?: unknown; title?: ReactNode | ((schema: ProSchema<any, { tooltip?: ReactNode; ... 4 more ...; defaultKeyWords?: string | undefined; } & Pick<...> & { ...; }, ProSchemaComponentTypes, FormFieldType | "text", unknown>, type: Pr... | -- |
| renderText | 修改的数据是会被 valueType 消费 | ((text: any, record: any, index: number, action: ProCoreActionType<{}>) => any) | -- |
| render | Render 方法只管理的只读模式,编辑模式需要使用 renderFormItem | ((dom: ReactNode, entity: any, index: number, action: ProCoreActionType<{}>, schema: { key?: Key | undefined; dataIndex?: unknown; title?: ReactNode | ((schema: ProSchema<...>, type: ProSchemaComponentTypes, dom: ReactNode) => ReactNode); ... 17 more ...; proFieldProps?: (ProFieldProps & Record<...>) | u... | -- |
| debounceTime | request防抖动时间 默认10 单位ms | number | -- |
| dependencies | 依赖字段的name,暂时只在拥有 request 的项目中生效,会自动注入到 params 中 | any[] | -- |
| ignoreFormItem | 忽略 FormItem,必须要和 renderFormItem 组件一起使用 | boolean | -- |
| hideInForm | 在 Form 中隐藏 | boolean | -- |
| width | -- | string | number | -- |
| defaultKeyWords | -- | string | -- |
| index | -- | number | -- |
| colSize | 每个表单占据的格子大小 | number | -- |
| initialValue | 搜索表单的默认值 | any | -- |
| convertValue | 获取时转化值,一般用于将数据格式化为组件接收的格式 | SearchConvertKeyFn | -- |
| transform | 提交时转化值,一般用于将值转化为提交的数据 | SearchTransformKeyFn | -- |
| order | Form 的排序 | number | -- |
| valueType | -- | "color" | "group" | "formList" | "formSet" | "divider" | "dependency" | "index" | "text" | "checkbox" | "option" | "radio" | "slider" | "switch" | "date" | "time" | "password" | ... 36 more ... | -- |