@rc-component/form

repository·master·Indexed 21 days ago

https://github.com/react-component/field-form

A performance-first React form state manager supporting field-level subscriptions, complex validation, nested structures, and lists. Compatible with both React DOM and React Native, it provides a declarative way to manage form state via Form and Field components, a programmatic API through the useForm hook, and support for dynamic fields using the List component.

Tokens
13.9K
Snippets
48
Records
61
Agent score
77%

What's inside @rc-component/form

  1. Customizing Rule Validators

    master
    When implementing a custom validator, it is strongly recommended to return a Promise instead of using the legacy callback pattern. If your validator returns an Error(message), rc-field-form will automatically handle it as a validation error.
  2. Migrating from rc-form to rc-field-form

    master

    If you are moving from the legacy rc-form to rc-field-form, note the following behavioral changes:

    1. Field Synchronization: Fields that have not been interacted with will no longer automatically sync with initialValues. To update a field value manually, use setFieldsValue.
    2. Field Removal: Removing a Field component no longer automatically cleans up its corresponding value in the form store.
    3. Nested Names: Use arrays for nested paths (e.g., ['user', 'name']) instead of dot-notation strings (e.g., 'user.name'). Dot-notation strings are now treated as literal keys.
    4. Validation API: validateFields now returns a Promise and no longer accepts a callback. Use async/await for cleaner logic. If a validator returns Error(message), the message is automatically extracted.
    5. Error Retrieval: getFieldsError now always returns an array [] instead of null when no errors exist.
    6. Value Preservation: The preserve prop defaults to false. Set it to true if you want to keep field values even when the field is unmounted.
    7. Event Triggers: setFields does not trigger onFieldsChange, and setFieldsValue does not trigger onValuesChange to prevent infinite loops and decouple logic.
    // New async/await pattern for validateFields
    async function validate() {
      try {
        const values = await form.validateFields();
        console.log(values);
      } catch (errorList) {
        // errorList is an array of { name, errors }
        errorList.forEach(({ name, errors }) => {
          // Handle errors
        });
      }
    }
  3. Migration: Key differences from rc-form

    master

    If you are migrating from rc-form to rc-field-form, note the following breaking changes:

    1. Initial Values: Fields no longer automatically sync with initialValues when un-touched. To change a field value, use setFieldsValue.
    2. Field Removal: Removing a field no longer automatically cleans up its related value in the form store. To disable this behavior (keep the value), use preserve={false} (which is the new default).
    3. Nested Names: Use arrays for nested paths (e.g., ['user', 'name']) instead of dot-notation strings (e.g., 'user.name'). Dot-notation strings are now treated as literal keys.
    4. Validation API: validateFieldsAndScroll has been removed. Use your own logic with refs to handle scrolling.
    5. Error Returns: getFieldsError now always returns an array [] instead of null when there are no errors.
    6. Async Validation: validateFields is now Promise-based. Use async/await and try/catch blocks.
    7. Event Triggers: setFields does not trigger onFieldsChange, and setFieldsValue does not trigger onValuesChange to prevent infinite loops and reduce coupling.
  4. Access Field meta information

    master

    The meta object provides the current state of the field. When using the render prop pattern, you receive this object:

    PropertyTypeDescription
    touchedbooleantrue if the field has been interacted with.
    validatingbooleantrue if validation is currently in progress.
    errorsstring[]An array of error messages.
    warningsstring[]An array of warning messages.
    nameNamePathThe full path of the field in the form.
    validatedbooleantrue if validation has completed.
  5. Use the Field component with render props or children

    master

    The Field component manages individual field state, validation, and subscriptions. It can be used in two ways:

    1. As a wrapper: Pass a single React element as a child. The Field will automatically inject controlled props (like value and onChange) into that child.
    2. As a render prop: Pass a function as a child. This function receives control, meta, and the form instance, allowing for highly customized UI.

    control contains the props needed to drive the input (e.g., value, onChange), while meta contains the field's status (e.g., errors, touched, validating).

    // Option 1: Wrapper pattern
    <Field name="username">
      <Input />
    </Field>
    
    // Option 2: Render props pattern
    <Field name="username">
      {(control, meta, form) => (
        <div>
          <Input {...control} />
          {meta.errors.map(err => <span key={err}>{err}</span>)}
        </div>
      )}
    </Field>
  6. Use Form.List to manage dynamic field lists

    master

    The List component (typically accessed via Form.List) is used to manage dynamic arrays of fields within a form. It provides a render prop pattern that gives you access to the current list of fields, operations to modify the list (add, remove, move), and metadata about the list field.

    Key Requirements:

    • The children prop must be a function. Passing any other type will trigger a warning and return null.
    • The underlying form value for the provided name must be an array. If the value is not an array, a warning will be issued in non-production environments.

    Render Prop Arguments:

    • fields: ListField[]: An array of objects representing each item in the list. Each object contains:
      • name: The index of the field in the list.
      • key: A unique identifier for the field (used for stable rendering).
      • isListField: A boolean indicating it is part of a list.
    • operations: ListOperations: Functions to mutate the list:
      • add(defaultValue?: StoreValue, index?: number): Adds a new item. If index is provided and valid, it inserts at that position; otherwise, it appends to the end.
      • remove(index: number | number[]): Removes items at the specified index or indices.
      • move(from: number, to: number): Moves an item from one position to another.
    • meta: Meta: Metadata about the field (e.g., validation status).
    <Form.List name="users">
      {(fields, operations, meta) => (
        <>
          {fields.map(({ key, name, isListField }) => (
            <Form.Item
              key={key}
              name={[name, 'firstName']}
              rules={[{ required: true, message: 'Missing firstName' }]}
            >
              <Input />
            </Form.Item>
          ))}
          <Button onClick={() => operations.add()}>Add User</Button>
        </>
      )}
    </Form.List>
  7. Use the selector pattern with useWatch

    master

    The useWatch hook allows you to monitor specific field values within a form. To optimize performance and prevent unnecessary re-renders of a component when unrelated form fields change, you can provide a selector function. The selector function receives the entire form values object and returns only the specific slice of data the component needs to watch. The component will only re-render when the value returned by the selector changes.

    // Example pattern for optimized useWatch
    const value = useWatch({
      name: 'fieldName',
      selector: (values) => values.fieldName,
    });
  8. Clear field values on component unmount with clearOnDestroy

    master

    In @rc-component/form, you can control whether a field's value is automatically removed from the form instance when the field component is unmounted. By default, unmounting a field does not clear its value from the form state. To ensure the value is cleared when the component is destroyed, use the clearOnDestroy prop on the field component.

    This is useful in scenarios where a field is conditionally rendered and its presence in the form state should strictly correspond to its presence in the UI.

    // Example usage of clearOnDestroy
    <Form.Item name="username">
      <Input clearOnDestroy />
    </Form.Item>