react-component/field-form

๐Ÿ“ Performance-first React form state manager with field subscriptions, validation, lists, dependencies, and hook APIs.

TypeScript

1,028

502 commits

updated Oct 13, 2026

See the code

README

@rc-component/form

Ant Design Part of the Ant Design ecosystem.

๐Ÿ“ Performance-first React form state manager with field subscriptions, validation, lists, dependencies, and hook APIs.

NPM version npm downloads build status Codecov bundle size dumi

English | ็ฎ€ไฝ“ไธญๆ–‡

Highlights

AreaSupport
StateField-level subscriptions, controlled fields, initial values
ValidationRule-based validation powered by @rc-component/async-validator
StructureNested names, lists, dependencies, and preservation control
APIsForm, Field, List, FormProvider, useForm, and useWatch
RuntimeReact DOM and React Native friendly form state model

Install

npm install @rc-component/form

The package was formerly documented as rc-field-form; use @rc-component/form for new installs.

Usage

import Form, { Field } from '@rc-component/form';

const Input = ({ value = '', ...props }) => <input value={value} {...props} />;

const Demo = () => {
  return (
    <Form
      onFinish={values => {
        console.log('Finish:', values);
      }}
    >
      <Field name="username">
        <Input placeholder="Username" />
      </Field>
      <Field name="password">
        <Input placeholder="Password" />
      </Field>

      <button>Submit</button>
    </Form>
  );
};

export default Demo;

Examples

Run the local dumi site:

npm install
npm start

Then open http://localhost:8000.

API

We use typescript to create the Type definition. You can view directly in IDE. But you can still check the type definition here.

Form

PropDescriptionTypeDefault
componentCustomize Form render componentstring | Component | falseform
fieldsControl Form fields status. Only use when in ReduxFieldData[]-
formSet form instance created by useFormFormInstanceForm.useForm()
initialValuesInitial value of FormObject-
nameConfig name with FormProviderstring-
preservePreserve value when field removedbooleanfalse
validateMessagesSet validate message templateValidateMessages-
onFieldsChangeTrigger when any value of Field changed(changedFields, allFields) => void-
onFinishTrigger when form submit and success(values) => void-
onFinishFailedTrigger when form submit and failed({ values, errorFields, outOfDate }) => void-
onValuesChangeTrigger when any value of Field changed(changedValues, values) => void-

Field

PropDescriptionTypeDefault
dependenciesWill re-render if dependencies changedNamePath[]-
getValueFromEventSpecify how to get value from event(..args: any[]) => any-
getValuePropsCustomize additional props with value. This prop will disable valuePropName(value) => any-
initialValueField initial valueany-
nameField name pathNamePath-
normalizeNormalize value before update(value, prevValue, prevValues) => any-
preservePreserve value when field removedbooleanfalse
rulesValidate rulesRule[]-
shouldUpdateCheck if Field should updateboolean | (prevValues, nextValues) => boolean-
triggerCollect value update by event triggerstringonChange
validateTriggerConfig trigger point with rule validatestring | string[]onChange
valuePropNameConfig value mapping prop with elementstringvalue

List

PropDescriptionTypeDefault
nameList field name pathNamePath[]-
childrenRender props for listing fields(fields: { name: NamePath }[], operations: ListOperations) => ReactNode-

useForm

Form creates a form instance with Form.useForm by default. You can also create the instance yourself and pass it to Form to call methods directly.

const Demo = () => {
  const [form] = Form.useForm();
  return <Form form={form} />;
};

For class components, use ref to get the form instance:

class Demo extends React.Component {
  setRef = form => {
    // Form instance here
  };
  render() {
    return <Form ref={this.setRef} />;
  }
}
PropDescriptionType
getFieldValueGet field value by name path(name: NamePath) => any
getFieldsValueGet list of field values by name path list(nameList?: (NamePath[]) => any) | true
getFieldErrorGet field errors by name path(name: NamePath) => string[]
getFieldsErrorGet list of field errors by name path list(nameList?: NamePath[]) => FieldError[]
isFieldsTouchedCheck if list of fields are touched(nameList?: NamePath[], allTouched?: boolean) => boolean
isFieldTouchedCheck if a field is touched(name: NamePath) => boolean
isFieldValidatingCheck if a field is validating(name: NamePath) => boolean
resetFieldsReset fields status(fields?: NamePath[]) => void
setFieldsSet fields status(fields: FieldData[]) => void
setFieldsValueSet fields value(values) => void
submitTrigger form submit() => void
validateFieldsTrigger fields to validate(nameList?: NamePath[], options?: ValidateOptions) => Promise

FormProvider

PropDescriptionTypeDefault
validateMessagesConfig global validateMessages templateValidateMessages-
onFormChangeTrigger by named form fields change(name, { changedFields, forms }) => void-
onFormFinishTrigger by named form fields finish(name, { values, forms }) => void-

Interface

NamePath

Type
string | number | (string | number)[]

FieldData

PropType
touchedboolean
validatingboolean
errorsstring[]
namestring | number | (string | number)[]
valueany

Rule

PropType
enumany[]
lennumber
maxnumber
messagestring
minnumber
patternRegExp
requiredboolean
transform(value) => any
typestring
validator(rule, value, callback: (error?: string) => void, form) => Promise | void
whitespaceboolean
validateTriggerstring | string[]

validator

To keep sync with rc-form legacy usage of validator, we still provides callback to trigger validate finished. But in rc-field-form, we strongly recommend to return a Promise instead.

ListOperations

PropType
add(initValue: any) => void
remove(index: number) => void

ValidateMessages

Validate Messages provides a list of error template. You can ref here for fully default templates.

PropDescription
enumRule enum prop
lenRule len prop
maxRule max prop
minRule min prop
nameField name
patternRule pattern prop
typeRule type prop

Different with rc-form

rc-field-form is try to keep sync with rc-form in api level, but there still have something to change:

1. Field will not keep snyc with initialValues when un-touched

In rc-form, field value will get from initialValues if user not operate on it. It's a bug but user use as a feature which makes fixing will be a breaking change and we have to keep it. In Field Form, this bug will not exist anymore. If you want to change a field value, use setFieldsValue instead.

We do lots of logic to clean up the value when Field removed before. But with user feedback, remove exist value increase the additional work to keep value back with conditional field.

3. Nest name use array instead of string

In rc-form, we support like user.name to be a name and convert value to { user: { name: 'Bamboo' } }. This makes '.' always be the route of variable, this makes developer have to do additional work if name is real contains a point like app.config.start to be app_config_start and parse back to point when submit.

Field Form will only trade ['user', 'name'] to be { user: { name: 'Bamboo' } }, and user.name to be { ['user.name']: 'Bamboo' }.

4. Remove validateFieldsAndScroll

Since findDomNode is marked as warning in StrictMode. It seems over control of Form component. We decide to remove validateFieldsAndScroll method and you should handle it with you own logic:

<Form>
  <Field name="username">
    <input ref={this.inputRef} />
  </Field>
</Form>

5. getFieldsError always return array

rc-form returns null when no error happen. This makes user have to do some additional code like:

(form.getFieldsError('fieldName') || []).forEach(() => {
  // Do something...
});

Now getFieldsError will return [] if no errors.

6. Remove callback with validateFields

Since ES8 is support async/await, that's no reason not to use it. Now you can easily handle your validate logic:

async function() {
  try {
    const values = await form.validateFields();
    console.log(values);
  } catch (errorList) {
    errorList.forEach(({ name, errors }) => {
      // Do something...
    });
  }
}

Notice: Now if your validator return an Error(message), not need to get error by e => e.message. FieldForm will handle this.

7. preserve is default to false

In rc-form you should use preserve to keep a value cause Form will auto remove a value from Field removed. Field Form will always keep the value in the Form whatever Field removed. But you can still use preserve=false to disable value keeping since 1.5.0.

8. setFields not trigger onFieldsChange and setFieldsValue not trigger onValuesChange

In rc-form, we hope to help user auto trigger change event by setting to make redux dispatch easier, but it's not good design since it makes code logic couping.

Additionally, user control update trigger onFieldsChange & onValuesChange event has potential dead loop risk.

Development

npm install
npm start
npm test
npm run lint
npm run tsc
npm run compile
npm run build

The dumi site runs at http://localhost:8000 by default.

Release

npm run prepublishOnly

The release flow is handled by @rc-component/np through the rc-np command after the package build.

License

@rc-component/form is released under the MIT license.

antd
ant-design
javascript
react
react-component

react-component/field-form

๐Ÿ“ Performance-first React form state manager with field subscriptions, validation, lists, dependencies, and hook APIs.

TypeScript

1,028

502 commits

updated Oct 13, 2026

See the code

README

@rc-component/form

Ant Design Part of the Ant Design ecosystem.

๐Ÿ“ Performance-first React form state manager with field subscriptions, validation, lists, dependencies, and hook APIs.

NPM version npm downloads build status Codecov bundle size dumi

English | ็ฎ€ไฝ“ไธญๆ–‡

Highlights

AreaSupport
StateField-level subscriptions, controlled fields, initial values
ValidationRule-based validation powered by @rc-component/async-validator
StructureNested names, lists, dependencies, and preservation control
APIsForm, Field, List, FormProvider, useForm, and useWatch
RuntimeReact DOM and React Native friendly form state model

Install

npm install @rc-component/form

The package was formerly documented as rc-field-form; use @rc-component/form for new installs.

Usage

import Form, { Field } from '@rc-component/form';

const Input = ({ value = '', ...props }) => <input value={value} {...props} />;

const Demo = () => {
  return (
    <Form
      onFinish={values => {
        console.log('Finish:', values);
      }}
    >
      <Field name="username">
        <Input placeholder="Username" />
      </Field>
      <Field name="password">
        <Input placeholder="Password" />
      </Field>

      <button>Submit</button>
    </Form>
  );
};

export default Demo;

Examples

Run the local dumi site:

npm install
npm start

Then open http://localhost:8000.

API

We use typescript to create the Type definition. You can view directly in IDE. But you can still check the type definition here.

Form

PropDescriptionTypeDefault
componentCustomize Form render componentstring | Component | falseform
fieldsControl Form fields status. Only use when in ReduxFieldData[]-
formSet form instance created by useFormFormInstanceForm.useForm()
initialValuesInitial value of FormObject-
nameConfig name with FormProviderstring-
preservePreserve value when field removedbooleanfalse
validateMessagesSet validate message templateValidateMessages-
onFieldsChangeTrigger when any value of Field changed(changedFields, allFields) => void-
onFinishTrigger when form submit and success(values) => void-
onFinishFailedTrigger when form submit and failed({ values, errorFields, outOfDate }) => void-
onValuesChangeTrigger when any value of Field changed(changedValues, values) => void-

Field

PropDescriptionTypeDefault
dependenciesWill re-render if dependencies changedNamePath[]-
getValueFromEventSpecify how to get value from event(..args: any[]) => any-
getValuePropsCustomize additional props with value. This prop will disable valuePropName(value) => any-
initialValueField initial valueany-
nameField name pathNamePath-
normalizeNormalize value before update(value, prevValue, prevValues) => any-
preservePreserve value when field removedbooleanfalse
rulesValidate rulesRule[]-
shouldUpdateCheck if Field should updateboolean | (prevValues, nextValues) => boolean-
triggerCollect value update by event triggerstringonChange
validateTriggerConfig trigger point with rule validatestring | string[]onChange
valuePropNameConfig value mapping prop with elementstringvalue

List

PropDescriptionTypeDefault
nameList field name pathNamePath[]-
childrenRender props for listing fields(fields: { name: NamePath }[], operations: ListOperations) => ReactNode-

useForm

Form creates a form instance with Form.useForm by default. You can also create the instance yourself and pass it to Form to call methods directly.

const Demo = () => {
  const [form] = Form.useForm();
  return <Form form={form} />;
};

For class components, use ref to get the form instance:

class Demo extends React.Component {
  setRef = form => {
    // Form instance here
  };
  render() {
    return <Form ref={this.setRef} />;
  }
}
PropDescriptionType
getFieldValueGet field value by name path(name: NamePath) => any
getFieldsValueGet list of field values by name path list(nameList?: (NamePath[]) => any) | true
getFieldErrorGet field errors by name path(name: NamePath) => string[]
getFieldsErrorGet list of field errors by name path list(nameList?: NamePath[]) => FieldError[]
isFieldsTouchedCheck if list of fields are touched(nameList?: NamePath[], allTouched?: boolean) => boolean
isFieldTouchedCheck if a field is touched(name: NamePath) => boolean
isFieldValidatingCheck if a field is validating(name: NamePath) => boolean
resetFieldsReset fields status(fields?: NamePath[]) => void
setFieldsSet fields status(fields: FieldData[]) => void
setFieldsValueSet fields value(values) => void
submitTrigger form submit() => void
validateFieldsTrigger fields to validate(nameList?: NamePath[], options?: ValidateOptions) => Promise

FormProvider

PropDescriptionTypeDefault
validateMessagesConfig global validateMessages templateValidateMessages-
onFormChangeTrigger by named form fields change(name, { changedFields, forms }) => void-
onFormFinishTrigger by named form fields finish(name, { values, forms }) => void-

Interface

NamePath

Type
string | number | (string | number)[]

FieldData

PropType
touchedboolean
validatingboolean
errorsstring[]
namestring | number | (string | number)[]
valueany

Rule

PropType
enumany[]
lennumber
maxnumber
messagestring
minnumber
patternRegExp
requiredboolean
transform(value) => any
typestring
validator(rule, value, callback: (error?: string) => void, form) => Promise | void
whitespaceboolean
validateTriggerstring | string[]

validator

To keep sync with rc-form legacy usage of validator, we still provides callback to trigger validate finished. But in rc-field-form, we strongly recommend to return a Promise instead.

ListOperations

PropType
add(initValue: any) => void
remove(index: number) => void

ValidateMessages

Validate Messages provides a list of error template. You can ref here for fully default templates.

PropDescription
enumRule enum prop
lenRule len prop
maxRule max prop
minRule min prop
nameField name
patternRule pattern prop
typeRule type prop

Different with rc-form

rc-field-form is try to keep sync with rc-form in api level, but there still have something to change:

1. Field will not keep snyc with initialValues when un-touched

In rc-form, field value will get from initialValues if user not operate on it. It's a bug but user use as a feature which makes fixing will be a breaking change and we have to keep it. In Field Form, this bug will not exist anymore. If you want to change a field value, use setFieldsValue instead.

We do lots of logic to clean up the value when Field removed before. But with user feedback, remove exist value increase the additional work to keep value back with conditional field.

3. Nest name use array instead of string

In rc-form, we support like user.name to be a name and convert value to { user: { name: 'Bamboo' } }. This makes '.' always be the route of variable, this makes developer have to do additional work if name is real contains a point like app.config.start to be app_config_start and parse back to point when submit.

Field Form will only trade ['user', 'name'] to be { user: { name: 'Bamboo' } }, and user.name to be { ['user.name']: 'Bamboo' }.

4. Remove validateFieldsAndScroll

Since findDomNode is marked as warning in StrictMode. It seems over control of Form component. We decide to remove validateFieldsAndScroll method and you should handle it with you own logic:

<Form>
  <Field name="username">
    <input ref={this.inputRef} />
  </Field>
</Form>

5. getFieldsError always return array

rc-form returns null when no error happen. This makes user have to do some additional code like:

(form.getFieldsError('fieldName') || []).forEach(() => {
  // Do something...
});

Now getFieldsError will return [] if no errors.

6. Remove callback with validateFields

Since ES8 is support async/await, that's no reason not to use it. Now you can easily handle your validate logic:

async function() {
  try {
    const values = await form.validateFields();
    console.log(values);
  } catch (errorList) {
    errorList.forEach(({ name, errors }) => {
      // Do something...
    });
  }
}

Notice: Now if your validator return an Error(message), not need to get error by e => e.message. FieldForm will handle this.

7. preserve is default to false

In rc-form you should use preserve to keep a value cause Form will auto remove a value from Field removed. Field Form will always keep the value in the Form whatever Field removed. But you can still use preserve=false to disable value keeping since 1.5.0.

8. setFields not trigger onFieldsChange and setFieldsValue not trigger onValuesChange

In rc-form, we hope to help user auto trigger change event by setting to make redux dispatch easier, but it's not good design since it makes code logic couping.

Additionally, user control update trigger onFieldsChange & onValuesChange event has potential dead loop risk.

Development

npm install
npm start
npm test
npm run lint
npm run tsc
npm run compile
npm run build

The dumi site runs at http://localhost:8000 by default.

Release

npm run prepublishOnly

The release flow is handled by @rc-component/np through the rc-np command after the package build.

License

@rc-component/form is released under the MIT license.

antd
ant-design
javascript
react
react-component

Project health

Overall

5.2/10

Maintained

5/10

Code review

2/10
OpenSSF Scorecard

Significant stargazers

Abraham

336 followers ยท starred Jan 2021

stygian-desolator

10 followers ยท starred Jun 2022

ZIce

31 followers ยท starred Jan 2025