Form Configuration

Framework:

Schema FormX uses the schema configuration object to define the structure, behavior, and layout of a form. This chapter explains the basic concepts and usage of schema configuration.

Form Component Props

PropTypeRequiredDescription
schemaFieldSchema[] | GroupSchema[]YesForm field configuration
layoutLayoutYesLayout configuration
componentsobjectYesCustom component mapping
dataobjectNoControlled data
defaultDataobjectNoDefault data
onChangefunctionNoData change callback

Basic Concepts

Schema

Schema is the core of form configuration, consisting of an array of FieldSchema objects. Each FieldSchema defines a form field.

Example:

const schema = [
  {
    type: 'string',
    name: 'username',
    label: 'Username',
    required: true
  },
  {
    type: 'string',
    name: 'email',
    label: 'Email',
    required: true
  }
];

<Form schema={schema} />

FieldSchema

FieldSchema is the configuration object for a single form field, containing the following basic properties:

PropertyTypeRequiredDescription
typeFieldTypeNoField type, defaults to 'string'
namestringYesField name, used for data binding
labelstringYesField display name
requiredbooleanNoWhether the field is required
componentstringNoCustom component name
propsobjectNoComponent props
hiddenbooleanNoWhether the field is hidden
depsstring[]NoDependent field list
onDepsChangeFunctionNoDependency change callback
validatorFunctionNoValidation function

Components

The components mapping is used to register custom form controls. Each component needs to match the field's component property.

Example:

const InputComponent = ({ value, onChange, ...props }) => (
  <input
    value={value || ''}
    onChange={(e) => onChange?.(e.target.value)}
    placeholder={props.placeholder}
  />
);

const SelectComponent = ({ value, onChange, options, ...props }) => (
  <select
    value={value || ''}
    onChange={(e) => onChange?.(e.target.value)}
  >
    {options?.map(option => (
      <option key={option} value={option}>{option}</option>
    ))}
  </select>
);

const components = {
  DefaultControl: InputComponent,
  MySelect: SelectComponent
};

<Form components={components} />

Layout

The layout configuration is used to control the styles and arrangement of the form. You can set CSS class names and inline styles for the form, groups, fields, labels, and controls.

Example:

const layout = {
  formClassName: 'my-form',
  formStyle: {
    maxWidth: '600px',
    margin: '0 auto'
  },
  fieldClassName: 'form-field',
  fieldStyle: {
    marginBottom: '16px'
  },
  labelClassName: 'field-label',
  labelStyle: {
    display: 'block',
    marginBottom: '6px',
    fontWeight: '500'
  },
  controlClassName: 'field-control',
  controlStyle: {
    width: '100%'
  }
};

<Form layout={layout} />

Complete Example

Form Configuration

Data Management Modes

Schema FormX supports two data management modes:

Use defaultData to set initial values, the form manages data state internally:

<Form 
  schema={schema} 
  defaultData={{ name: 'John', age: 25 }}
  onChange={({ data }) => console.log('Data changed:', data)}
/>

Characteristics:

  • Simple to use, no manual state sync needed
  • Suitable for most scenarios
  • Use ref to call getData()/setData() for data operations

Controlled Mode

Use data + onChange to fully control form data:

const [formData, setFormData] = useState({ name: '', age: 0 });

<Form 
  schema={schema} 
  data={formData}
  onChange={({ data }) => setFormData(data)}
/>

Characteristics:

  • External full control of data flow
  • Suitable for integration with external state (e.g., Redux, URL params)
  • data in onChange callback is frozen, shallow copy before modifying

Mode Comparison

AspectUncontrolledControlled
Data ManagementInternalExternal state
PropsdefaultDatadata + onChange
ComplexityLowMedium
Use CaseSimple formsComplex business

Note: data and defaultData can be used together. When both are provided, data takes priority, and defaultData serves as the baseline for isDirty() comparison. For example, you can set a baseline with defaultData in controlled mode to detect whether form data has been modified.

Controlled vs Uncontrolled

Uncontrolled Form - Use defaultData to set initial values, and the form manages data automatically:

<Form 
  schema={schema} 
  components={components}
  defaultData={{ username: 'John', email: 'john@example.com' }}
/>

Controlled Form - Use data to control the form data:

const [formData, setFormData] = useState({ username: '', email: '' });

<Form 
  schema={schema} 
  components={components}
  data={formData}
  onChange={({ data }) => setFormData(data)}
/>

Form Reference

Get the form instance via ref to call instance methods:

import { useRef } from 'react';

const formRef = useRef(null);

// Get data
const data = formRef.current.getData();

// Set data
formRef.current.setData({ username: 'new value' });

// Reset form
formRef.current.reset();

// Validate form
const { data, errors } = await formRef.current.validate();

// Check if modified
const isDirty = formRef.current.isDirty();

<Form ref={formRef} schema={schema} components={components} />

Best Practices

Schema Structure

  • Keep the schema simple and clear
  • Use type to define field types
  • Use props to pass component-specific props
  • Use hidden to dynamically control field visibility

Component Design

  • Components should support value and onChange props
  • Handle null and undefined values
  • Support common HTML attributes
  • Provide clear placeholder hints

Data Flow

  • Prefer uncontrolled mode for simpler usage
  • Use controlled mode when needing precise data control
  • Use onChange callback to listen for data changes
  • Use ref methods to programmatically operate the form

Note on onChange timing: The onChange callback fires immediately on every field value change (not on blur). Each value change also triggers dependency linkage and field validation synchronously.

Note on hidden fields: Setting hidden: true on a field only hides it visually (display: none). Hidden fields still participate in validation — if a hidden field has required: true, validation errors will still be raised. Use conditional required or a custom validator to skip validation for hidden fields.