Skip to main content

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:

  • title corresponds to Form.Item's label.
  • dataIndex corresponds to Form.Item's name.
  • formItemProps values are passed to the Form.Item component.
  • fieldProps values 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.

option 1

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:

Please select

Horizontal Grid Alignment

In horizontal grid mode, label widths may vary:

Please select

Solutions include setting minimum widths:

Please select

Or specifying individual widths:

Please select

labelCol is 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:

  1. Use render (not renderFormItem) for custom readonly displays
  2. Return null from render for empty space: render: () => null

💡 Rakjs extends the readonly render function's second parameter (record) to include all form values.

a1with ID: 1
-
-
-
-

Readonly Table Mode

Readonly mode can also be transformed into a table-like format, similar to the Antd Descriptions component.

a1with ID: 1
-
-
-
-

Field Dependencies

Initial Values and Value Assignment

  1. [Show User] defaults to No

  2. Toggles to Yes when clicking "Set Values"

  3. 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

ID: -, Name: -, Hobbies: -

Form Data

Value Retrieval

SchemaForm provides multiple value retrieval methods. The most common is onFinish.

Other methods via form instance:

Form Instance MethodDescriptionSource
getFieldsValueGet form valuesAntd Form
validateFieldsReturn form values after validation passesAntd Form
getFieldsFormatValueGet form values (will additionally process time format according to valueType; will also process transform in schema)ProComponent
onFinishSame as aboveProComponent
validateFieldsReturnFormatValueReturn form values after validation passes (with additional transformation as aboveProComponent

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 MethodResult
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 value and label, you can customize them using an underscore. For example, userId,userName_id,name splits 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.
Please select

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.

Please select

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: ... }`
Basic Information
Business Information
Please select
Please select
基本信息
业务信息

👉 Important note: In embed mode, the valueBaseName implementation 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 valueBaseName has a value, the name property 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.

Basic Information
Additional Information
Please select

Group Layout grid

The group layout operates at two levels:

  1. Outer layer (section title)

  2. Inner layer (form items within columns)

Accordingly, colProps configurations must be specified separately for each level.

Basic Information
Additional Information
Please select
Basic Information
Additional Information
Please select

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 itemRender function signature: ({ listDom, action }, options) => ReactNode

The options signature: {name, field, index, record, fields, operations, meta}

1

API

SchemaForm

SchemaForm type is a combination of SchemaFormSelfType and SchemaFormOriginType.

SchemaFormSelfType

PropertyDescriptionTypeDefault
embedWhether it is embed modebooleanfalse
valueBaseNameWhen embed is enabled, handle nested data structure; when collecting data in onFinish, it will be attached under this field. Only applicable in embed mode.stringfalse
readonlyWhether it is readonly modebooleanfalse
readonlyTypeDisplay type when readonly. Options: form or descriptions"form" | "descriptions"form
columnsConfiguration description of form items;Required
onFinishCallback when the form is submitted;(values) => Promise | void--
formRefUsed 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>--
innerRefRAK specific ref, used to store some utility functions and data.--
submitterSubmit button related configuration.boolean | SubmitterProps & { style: React.CSSProperties }false
descriptionsPropsTable style configuration in descriptions mode
Omit<DescriptionsProps, 'items' | 'columns'>
--

SchemaFormOriginType

PropertyDescriptionTypeDefault
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--
hideRequiredMarkWill 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获取 ProFormInstanceMutableRefObject<any> | RefObject<any>--
syncToUrl同步结果到 url 中boolean | ((values: Record<string, any>, type: "get" | "set") => Record<string, any>)--
syncToUrlAsImportant当 syncToUrl 为 true,在页面回显示时,以url上的参数为主,默认为falseboolean--
extraUrlParams额外的 url 参数 中Record<string, any>--
syncToInitialValues同步结果到 initialValues,默认为true如果为false,reset的时将会忽略从url上获取的数据boolean--
omitNil如果为 false,会原样保存。booleantrue
dateFormatter格式化 Date 的方式,默认转化为 stringfalse | "string" | "number" | (string & {}) | ((value: Dayjs, valueType: string) => string | number)--
onInit表单初始化成功,比如布局,label等计算完成((values: Record<string, any>, form: ProFormInstance<any>) => void)--
params发起网络请求的参数Record<string, any>--
request发起网络请求的参数,返回值会覆盖给 initialValuesProRequestData<Record<string, any>, Record<string, any>>--
isKeyPressSubmit是否回车提交boolean--
formKey用于控制form 是否相同的key,高阶用法string--
autoFocusFirstInput--boolean--
readonly是否只读模式,对所有表单项生效boolean--
gridopen grid layoutbooleanfalse
colPropsonly works when grid is enabledColProps{ xs: 24 }
rowPropsonly works when grid is enabledRowProps{ gutter: 8 }

SchemaFormInnerRefType

ParameterDescriptionTypeDefault
dataCan be used to store extra data in the formRecord<string, any>{}
setDataStore 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

PropertyDescriptionTypeDefault
fieldPropsInject innerRef into the fieldProps methodobject | ((form: ProFormInstance, innerRef: BaseInnerRef, config: any) => object)--
renderFormItemInject innerRef into the renderFormItem method((item: any, config: any, form: any, innerRef: BaseInnerRef) => any)--
columnsRedefine the columns typeFormColumnType<any, "text">[] | ((values: any) => FormColumnType<any, "text">[])--
requiredWhether it is required; shorthand for formItemProps: { rules: [{ required: true }] }boolean--
dataIndexCan 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从服务器请求的参数,改变了会触发 reloadRecord<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--
colPropsonly works when grid is enabledColProps{ xs: 24 }
rowPropsonly works when grid is enabledRowProps{ 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 是支持其他基础类型作为 keyProSchemaValueEnumObj | ProSchemaValueEnumMap | ((row: any) => ProSchemaValueEnumObj | ProSchemaValueEnumMap)--
formItemProps自定义的 formItemPropsFormItemProps<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)--
renderRender 方法只管理的只读模式,编辑模式需要使用 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...--
debounceTimerequest防抖动时间 默认10 单位msnumber--
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--
orderForm 的排序number--
valueType--"color" | "group" | "formList" | "formSet" | "divider" | "dependency" | "index" | "text" | "checkbox" | "option" | "radio" | "slider" | "switch" | "date" | "time" | "password" | ... 36 more ...--