Form is the core component of Schema FormX, used to render and manage forms.
Import
import { Form } from '@schema-formx/react';
Props
schema
Form field configuration, can be an array of fields or groups.
type Schema = FieldSchema[] | GroupSchema[];
Type: FieldSchema[] | GroupSchema[]
Required: Yes
Example:
// Field array
const schema: FieldSchema[] = [
{ type: 'string', name: 'name', label: 'Name' },
{ type: 'string', name: 'email', label: 'Email' }
];
// Group array
const schema: GroupSchema[] = [
{
key: 'basic',
props: { title: 'Basic Info' },
fields: [
{ type: 'string', name: 'name', label: 'Name' }
]
}
];
<Form schema={schema} />
layout
Layout configuration, controls the form's styles and arrangement.
Type: Layout
Required: Yes
Type Definition:
type Layout = {
formClassName?: string;
formStyle?: CSSProperties;
groupClassName?: string;
groupStyle?: CSSProperties;
fieldClassName?: string;
fieldStyle?: CSSProperties;
labelClassName?: string;
labelStyle?: CSSProperties;
tipClassName?: string;
tipStyle?: CSSProperties;
controlClassName?: string;
controlStyle?: CSSProperties;
groups?: Record<string, SetUI<'group'>>;
fields?: Record<string, SetUI<'field' | 'label' | 'control' | 'tip'>>;
};
Example:
const layout: Layout = {
formClassName: 'my-form',
formStyle: { maxWidth: '600px' },
fieldClassName: 'form-field',
fieldStyle: { marginBottom: '16px' },
labelClassName: 'field-label',
labelStyle: { fontWeight: '500' }
};
<Form layout={layout} />
components
Custom component mapping, used to register custom form controls and group containers.
Type: object
Required: Yes
Type Definition:
type Components = {
DefaultControl: FieldComponent;
DefaultGroup?: GroupComponent;
[key: string]: FieldComponent | GroupComponent | undefined;
};
Example:
const components = {
DefaultControl: MyInputComponent,
CustomSelect: MySelectComponent,
DefaultGroup: MyGroupComponent
};
<Form components={components} />
data
Controlled data, used to fully control the form data.
Type: Record<string, unknown>
Required: No
Example:
const [formData, setFormData] = useState({ name: '', email: '' });
<Form
data={formData}
onChange={({ data }) => setFormData(data)}
/>
defaultData
Default data, used to set initial values for the form.
Type: Record<string, unknown>
Required: No
Example:
const defaultData = {
name: 'John',
email: 'john@example.com'
};
<Form defaultData={defaultData} />
onChange
Data change callback, triggered when form data changes.
Type: function
Required: No
Notes:
onChange fires immediately on every field value change (not on blur)
- Each value change also triggers dependency linkage and field validation
- The
data parameter in the callback is frozen (Object.freeze); shallow-copy before modifying
Example:
<Form
onChange={({ data, name, value }) => {
console.log('Field changed:', name, value);
console.log('Current data:', data);
}}
/>
Instance Methods
Get the form instance via ref to call the following methods:
getData
Get the form data. The returned object is frozen with Object.freeze, and directly modifying it will throw a runtime error.
type GetData = () => Record<string, unknown>;
Notes:
- The returned data is a frozen object and cannot be directly modified
- If you need to modify the data, create a shallow copy first:
const data = { ...formRef.current.getData() }
- The
data parameter in the onChange callback is also frozen
Example:
const formRef = useRef<FormInstance>(null);
const handleGetData = () => {
const data = formRef.current.getData();
console.log('Form data:', data);
// ⚠️ data.name = 'new' will throw TypeError
// ✅ Correct: const copy = { ...data }; copy.name = 'new';
};
<Form ref={formRef} schema={schema} />
setData
Set the form data.
type SetData = (data: Record<string, unknown>) => void;
Notes:
- Calling
setData will automatically trigger dependency linkage (onDepsChange) for all fields, ensuring linked fields' schema and data remain consistent
- The passed data will completely replace the existing form data (not a shallow merge)
setData does NOT trigger the onChange callback and does NOT trigger validation. If validation is needed, call validate() manually afterwards
Example:
const formRef = useRef<FormInstance>(null);
const handleSetData = () => {
formRef.current.setData({
name: 'Jane',
email: 'jane@example.com'
});
};
<Form ref={formRef} schema={schema} />
reset
Reset the form to the default data.
Notes:
- Calling
reset will automatically trigger dependency linkage (onDepsChange) for all fields, ensuring linked fields' schema and data are restored consistently
reset does NOT trigger the onChange callback and does NOT trigger validation. If validation is needed, call validate() manually afterwards
Example:
const formRef = useRef<FormInstance>(null);
const handleReset = () => {
formRef.current.reset();
};
<Form ref={formRef} schema={schema} />
validate
Validate the form, returns the validation result. Supports partial validation and related field error clearing.
type Validate = () => Promise<{
data: Record<string, unknown>;
errors: Record<string, string> | null;
}>;
Example:
const formRef = useRef<FormInstance>(null);
const handleSubmit = async () => {
const { data, errors } = await formRef.current.validate();
if (errors) {
console.log('Validation failed:', errors);
return;
}
console.log('Validation passed:', data);
};
<Form ref={formRef} schema={schema} />
isDirty
Check whether the form data has deviated from the initial values (compared against defaultData).
type IsDirty = () => boolean;
Equivalent value rules: When comparing, certain values are treated as equivalent based on field type:
For example, if a field in defaultData is an empty string '' and the current value is undefined (for a string type field), isDirty() returns false.
Example:
const formRef = useRef<FormInstance>(null);
const handleCheckDirty = () => {
const isDirty = formRef.current.isDirty();
console.log('Data has changed:', isDirty);
};
<Form ref={formRef} schema={schema} />
Layout Configuration
SetUI Type
SetUI is a utility type that generates prefixed ClassName and Style configurations.
type SetUI<Prefix extends string> = {
[Key in `${Prefix}${'ClassName' | 'Style'}`]?: Key extends `${Prefix}Style`
? CSSProperties
: string;
};
Example:
// SetUI<'form'> is equivalent to:
type FormUI = {
formClassName?: string;
formStyle?: CSSProperties;
};
Layout configuration controls the form's styles and arrangement. Supports form-level, group-level, and field-level configuration.
const layout = {
formClassName: 'my-form',
formStyle: { maxWidth: '600px', margin: '0 auto' }
};
Group Level
const layout = {
groupClassName: 'form-group',
groupStyle: { marginBottom: '24px', padding: '20px' }
};
Field Level
const layout = {
fieldClassName: 'form-field',
fieldStyle: { marginBottom: '16px' },
labelClassName: 'field-label',
labelStyle: { fontWeight: '500' },
controlClassName: 'field-control',
controlStyle: { width: '100%' }
};
Label Level
const layout = {
labelClassName: 'field-label',
labelStyle: {
display: 'block',
marginBottom: '6px',
fontWeight: '500',
color: '#333'
}
};
Tip Level
const layout = {
tipClassName: 'field-tip',
tipStyle: {
color: '#ff4d4f',
fontSize: '12px',
marginTop: '4px'
}
};
Control Level
const layout = {
controlClassName: 'field-control',
controlStyle: {
width: '100%'
}
};
Per-Group Configuration
const layout = {
groups: {
basic: {
groupClassName: 'basic-group',
groupStyle: { backgroundColor: '#f0f8ff' }
},
advanced: {
groupClassName: 'advanced-group',
groupStyle: { backgroundColor: '#fff8e1' }
}
}
};
Per-Field Configuration
const layout = {
fields: {
username: {
fieldClassName: 'username-field',
labelClassName: 'username-label',
controlClassName: 'username-control'
},
email: {
fieldClassName: 'email-field',
labelClassName: 'email-label',
controlClassName: 'email-control'
}
}
};
Rendered HTML Structure and CSS Attributes
The Form component automatically generates the following HTML structure and CSS data attributes, which can be used for external styling:
<!-- Rendered structure illustration -->
<form class="formClassName">
<div class="groupClassName"> <!-- Group container -->
<div class="fieldClassName">
<label data-required="true" for="fieldName">Field Label</label>
<div class="controlClassName" data-error="true">
<!-- Form control -->
<span class="tipClassName">Error message</span>
</div>
</div>
</div>
</form>
CSS Data Attributes:
Usage Example:
/* Add red asterisk to required field labels */
label[data-required]::after {
content: ' *';
color: red;
}
/* Add red border to control container with errors */
[data-error="true"] {
border: 1px solid #ff4d4f;
border-radius: 4px;
}
Usage Examples
Basic Usage
import { Form } from '@schema-formx/react';
import type { FieldSchema } from '@schema-formx/react';
const schema: FieldSchema[] = [
{ type: 'string', name: 'name', label: 'Name', required: true },
{ type: 'string', name: 'email', label: 'Email', required: true }
];
function MyForm() {
return <Form schema={schema} />;
}
import { useState } from 'react';
import { Form } from '@schema-formx/react';
function MyForm() {
const [formData, setFormData] = useState({ name: '', email: '' });
return (
<Form
schema={schema}
data={formData}
onChange={({ data }) => setFormData(data)}
/>
);
}
import { useRef } from 'react';
import { Form } from '@schema-formx/react';
import type { FormInstance } from '@schema-formx/react';
function MyForm() {
const formRef = useRef<FormInstance>(null);
const handleSubmit = async () => {
const { data, errors } = await formRef.current.validate();
if (errors) {
alert('Validation failed');
return;
}
console.log('Submit data:', data);
};
return (
<>
<Form ref={formRef} schema={schema} />
<button onClick={handleSubmit}>Submit</button>
</>
);
}