# rc-component/form subscribes per field, so a form with fifty inputs does not re-render fifty times

> The Ant Design ecosystem's form state manager, published as @rc-component/form after a rename from rc-field-form. The performance claim rests on field-level subscriptions and a separate useWatch hook, and the API surface is built around a Form component with Field, List and FormProvider.

**react-component/field-form** — 📝 Performance-first React form state manager with field subscriptions, validation, lists, dependencies, and hook APIs.

- Repository: https://github.com/react-component/field-form
- Website: https://react-component.github.io/field-form
- Stars: 1,028 · Forks: 287
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/react-component-field-form

## The rename is the first thing to get right

The package is published as `@rc-component/form`, and the README says it was formerly documented as `rc-field-form` and that you should use `@rc-component/form` for new installs.

That is a small sentence with a large practical consequence. The npm package name, the repository name and the documentation name are all different from each other, and the repository itself is `react-component/field-form` while the npm scope is `@rc-component`. A search for the old name lands you on documentation that describes a package you would have to install differently.

```bash
npm install @rc-component/form
```

The project is part of the Ant Design ecosystem, which the README states directly, and the Ant Design link in the header is the connection. The same pattern runs through the dependency list, where the validator is `@rc-component/async-validator` and the utilities are `@rc-component/util`, so the rc-component scope is the family's namespace rather than something specific to this package.

The description of what the library does is short. It is a performance-first React form state manager with field subscriptions, validation, lists, dependencies and hook APIs, and the highlights table breaks that into four areas: state with field-level subscriptions, controlled fields and initial values; validation powered by `@rc-component/async-validator`; structure with nested names, lists, dependencies and preservation control; and the APIs themselves, `Form`, `Field`, `List`, `FormProvider`, `useForm` and `useWatch`.

The last one in the runtime row matters more than it looks. The form state model is described as React DOM and React Native friendly, which is a consequence of the state living outside the React tree, and it is what makes the package usable in a React Native application where the DOM does not exist.

## The basic usage is a Form with Field children

The usage example is small and it shows the shape of the API.

```tsx
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>
  );
};
```

The design choice here is that a `Field` wraps the input rather than the input being configured directly. The `Field` declares the name, the validation rules and the dependencies, and the child is just a controlled input. The library injects the value and the change handler.

That is what makes the field-level subscription model possible. Because the state is keyed by field name rather than stored in a single form object that every component reads, a component that watches one field is not notified when a different field changes. The alternative, a form context holding all values, re-renders every subscriber on every keystroke, which is the performance problem the description is pointing at.

The `Field` props table makes the model explicit. `name` is a NamePath, so a field can be nested. `rules` holds the validation rules. `dependencies` will re-render if the dependencies changed, which is the mechanism for a field that depends on another field's value, such as a confirm password. `shouldUpdate` is a boolean or a function of previous and next values, which is the escape hatch for a field that needs to re-render on anything. And `trigger` sets the event that collects the value update, defaulting to `onChange`.

Two more props are worth noting because they are the ones people get wrong. `getValueFromEvent` specifies how to get the value from the event, and `normalize` normalises the value before update, with access to the previous value and previous values. Together they are how a select that returns an array, or a checkbox that returns a boolean, becomes a field the form can store as a plain value.

## Preservation decides whether a removed field's value survives

The `preserve` prop appears twice in the documentation, on `Form` and on `Field`, and it defaults to false in both cases. Its meaning is preserve value when field removed, and it is one of the props most likely to cause confusion.

When a field is removed from the tree, by a conditional render or by a step in a wizard, the default behaviour is that its value is dropped. With `preserve` set to true, the value stays in the form state even though the field is no longer mounted. That is what you want in a wizard where step two is hidden on step one and the values have to come back, and it is what you do not want if a hidden field is meant to be cleared.

The `Form` props table has the same idea at the top level, so a form can preserve everything or preserve nothing and let individual fields override.

The other Form props follow the same pattern of giving you a hook rather than a fixed behaviour. `component` customises the form render component, defaulting to `form`, and can be a string, a Component or false, where false means no form element is rendered at all. `fields` controls the form field status and is only for use in Redux. `form` sets the form instance created by `useForm`, and the default is `Form.useForm()`. `initialValues` sets the initial value of the form, and `name` configures a name with `FormProvider`.

The callbacks are the interesting part. `onValuesChange` triggers when any value of a field changed and receives the changed values and all values, while `onFieldsChange` does the same but for field status rather than values, which is the one you want for a form that shows a spinner next to a field being validated. `onFinish` triggers on successful submit, `onFinishFailed` on failure and receives an object with values, errorFields and outOfDate, and the `outOfDate` key is a subtle and useful detail: it tells you that a field changed while validation was running, which is the classic race in an async validated form.

`validateMessages` sets the validate message template, which is how you localise or reword the validation output.

## useWatch and FormProvider are how you reach state from outside

The API list in the highlights table includes `useForm`, `useWatch` and `FormProvider`, and those three are the hooks and context pieces that make the field-level model usable from outside a `Field`.

`useForm` creates the form instance that the `Form` component's `form` prop accepts, and the default for that prop is `Form.useForm()`, so the common case does not need the hook at all. You reach for it when you need the instance itself: to read values, to reset the form, to call validation, or to hand the instance to a `FormProvider`.

`useWatch` is the read side of the subscription model, and it is the piece that makes the performance claim real rather than theoretical. It subscribes to one field path and re-renders the calling component when that field changes, which means a summary panel, a character counter or a dependent select can read a value without subscribing to the whole form. The `Field` component's `dependencies` prop is the declarative equivalent, for the case where the dependency is known at render time.

`FormProvider` and the `name` prop on `Form` are how two separate `Form` trees can share one store. The `name` configures a name with `FormProvider`, so a nested form can be scoped under a parent form and read from it with `useWatch` on the qualified path. This is how a form split across components, or a modal with its own fields inside a page form, avoids duplicating state.

The `List` component completes the set. It is the array case, where a repeatable group of fields behaves like a list rather than a fixed set of named fields, and the `NamePath` type is what makes that composable: a field name is a path, so a list element's field is a path into the array, and the same subscription model applies at every depth.

Together these are the parts of the library that are not in the quick start example, and they are the parts a real application uses.

## The package ships three build outputs and a small dependency set

The packaging is worth a look because it shows the range of consumers the library is built for.

The `files` array includes `lib`, `es`, `dist` and `assets/*.css`, and the entry points reflect it, with `main` pointing at `./lib/index` as CommonJS and `module` at `./es/index` as ES modules. A `dist` directory alongside a `lib` and `es` pair is the convention for shipping a UMD or bundled build for script tag consumers, and the CSS files in the package are for the form's own styling.

The build is done with father, and the `compile` script runs `father build`, with the `prepublishOnly` script running `npm run compile && rc-np` so the package is compiled before it is published. The `build` script is `npm run docs:build`, which runs `dumi build`, and the docs are deployed with gh-pages, with a `GH_PAGES=1` variant of the docs build for the deployment environment. The `.dumirc.ts` file at the root configures dumi, and the local site is started with `npm start` and served at `http://localhost:8000`.

The runtime dependencies are three: `@rc-component/async-validator` for the rule based validation, `@rc-component/util` for the shared helpers, and `clsx` for class name composition. The validator is a separate package because the validation logic is genuinely reusable on its own, and the `Rule` type the documentation links to is defined there.

The peer dependencies are react and react-dom at 16.9.0 or newer, which is the floor for the hooks API. The `engines` field says node 8.x or newer, which is a much older floor than the peer range implies, and it reflects the age of the package rather than a tested configuration.

The tooling is conventional for this ecosystem: eslint with a flat config at `eslint.config.mjs`, prettier with its own config, husky for the git hooks with a `prepare` script that installs them, and jest through the `rc-test` wrapper, with the testing library packages for React and jest-dom.

The last push to the repository was on 2026-10-13 with the repository not archived, and the recent releases are `@rc-component/form@1.8.6` from 2026-07-27, `1.8.5` from 2026-06-16 and `1.8.4` from 2026-06-15.

## Conclusion

Adopt @rc-component/form if you need a form library with per field subscriptions, nested name paths, lists and dependencies, and you want to stay inside the Ant Design ecosystem. Do not adopt it if you want a small dependency footprint, since the validator and utility packages are real dependencies, or if you need the newest React features, since the peer range starts at React 16.9. Verify first that the version you install is the renamed @rc-component/form package rather than the older rc-field-form, that your React version satisfies the peer range, and that the field you care about re-renders in isolation in your own build rather than assuming the subscription model behaves as documented.

## FAQ

### What package name should I install for the rc-component form library?

Install @rc-component/form. The README says the package was formerly documented as rc-field-form and that you should use @rc-component/form for new installs, even though the repository is react-component/field-form.

### How does field level subscription differ from a form context?

State is keyed by field name, so a component watching one field is not notified when another changes, rather than every subscriber re-rendering on every keystroke through a single form context. The Field component declares a name and the library injects the value and change handler into the child input.

### What does the preserve prop do on Form and Field?

It preserves the value when a field is removed, and it defaults to false in both places. Set it to true when a hidden or unmounted field must keep its value, such as a wizard step, and leave it false when a hidden field should be cleared.

### What are the runtime and peer dependencies of @rc-component/form?

The runtime dependencies are @rc-component/async-validator for rule based validation, @rc-component/util for shared helpers and clsx for class name composition. React and react-dom are peer dependencies at 16.9.0 or newer, and the package requires Node 8.x or newer.

### How do I run the local documentation site for rc-component form?

Run npm install and npm start, which starts the dumi development server, then open http://localhost:8000. The production docs build is npm run build, which runs dumi build, and the publish step runs npm run compile with father before rc-np.

## Sources

- [License: MIT](https://github.com/react-component/field-form/blob/master/LICENSE)
- [Project website](https://react-component.github.io/field-form)
- [react-component/field-form on GitHub](https://github.com/react-component/field-form)
- [README](https://github.com/react-component/field-form/blob/master/README.md)
- [Releases](https://github.com/react-component/field-form/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/react-component-field-form
