React Final Form

repository·main·Indexed 27 days ago

https://github.com/final-form/react-final-form

A high-performance, subscription-based form state management library for React. It serves as a thin wrapper around Final Form, utilizing the Observer pattern to minimize re-renders by updating only components subscribed to specific state changes. Key features include the <Form />, <Field />, and <FormSpy /> components, as well as hooks like useField(), useForm(), and useFormState() for granular state control and custom field creation.

Tokens
14.7K
Snippets
31
Records
91
Agent score
92%

What's inside react-final-form

  1. Overview of React Final Form

    main

    React Final Form is a thin React wrapper for Final Form. It is a subscriptions-based form state management library that utilizes the Observer pattern. This design ensures that only the specific components that need updating are re-rendered when the form's state changes, optimizing performance.

    Key features include:

    • Zero dependencies that affect bundle size.
    • Peer dependencies: React and Final Form.
    • Opt-in subscriptions for granular updates.
  2. Understand the design goals of React Final Form

    main

    React Final Form is designed with four primary goals to address common form library concerns:

    • Strongly Typed: Provides support for Flow and Typescript to catch bugs during development.
    • Modularity: Complex functionality is broken out into separate packages. This prevents the core from becoming bloated and allows you to only include the features required for your specific use case.
    • Minimal Bundle Size: It acts as a minimal wrapper around the zero-dependency final-form core. Its primary responsibilities are extracting values from React SyntheticEvent objects and managing field subscriptions.
    • High Performance: Supports fine-grained rerendering. You can choose exactly which pieces of form and field state trigger a rerender, similar to using selectors in Redux to subscribe to specific slices of state.
  3. Understand React Final Form Architecture

    main

    React Final Form is a thin React wrapper for final-form. It uses a subscriptions-based approach following the Observer pattern. This ensures that only the components subscribed to specific state changes re-render, rather than the entire form.

    While the library subscribes to all changes by default, you can optimize performance by specifying exactly which parts of the form state a component should subscribe to.

  4. Use Parse and Format Props for Data Normalization

    main

    Use the parse and format props on <Field /> to control data flow:

    • parse: Converts the value from the input (e.g., a string from a text box) into the format used in the form state.
    • format: Converts the value from the form state into the format required by the input component. This pattern is useful for 'normalizing' values (e.g., stripping whitespace or formatting numbers).
  5. Migrate react-final-form from v6 to v7

    main

    Version 7.0.0 is a complete TypeScript rewrite. While runtime behavior is largely unchanged, several TypeScript-specific breaking changes require updates to your code.

    Migration Steps:

    1. Update dependencies:
      npm install react-final-form@^7.0.0 final-form@^5.0.0
    2. Fix compilation errors in the following order:
      • Handle optional boolean properties in FormState using nullish coalescing (e.g., ?? false).
      • Replace FieldMetaState imports with FieldRenderProps['meta'].
      • Replace AnyObject imports with a local Record<string, any> definition.
      • Remove generics from UseFieldConfig<T> (use UseFieldConfig instead).
      • Remove arbitrary props (like style or className) from the <Form> component; move them to a wrapper <div> or the inner <form> element.
    3. Update mocks/tests:
      • Add asyncErrors: {} to InternalFormState mocks.
      • Cast mutators using as unknown as Mutator if type errors occur.
    npm install react-final-form@^7.0.0 final-form@^5.0.0
  6. Implement Field Arrays with `react-final-form-arrays`

    main

    React Final Form does not include field arrays in the core package to keep the bundle size small. To use <FieldArray/>, you must install final-form-arrays and react-final-form-arrays.

    Setup Requirements:

    • You must pass mutators={{ ...arrayMutators }} to the <Form/> component.
    • The form object in the render prop will then contain injected mutators like push and pop.
    • <FieldArray/> provides a fields array that you can map over to render individual <Field/> components using the field name (e.g., ${name}.fieldName).
    import arrayMutators from 'final-form-arrays'
    import { FieldArray } from 'react-final-form-arrays'
    
    const MyForm = () => (
      <Form
        onSubmit={onSubmit}
        mutators={{ ...arrayMutators }}
      >
        {({
          handleSubmit,
          form: {
            mutators: { push, pop }
          }
        }) => (
          <form onSubmit={handleSubmit}>
            <button type="button" onClick={() => push('customers', undefined)}>
              Add Customer
            </button>
            <button type="button" onClick={() => pop('customers')}>
              Remove Customer
            </button>
            <FieldArray name="customers">
              {({ fields }) =>
                fields.map((name, index) => (
                  <div key={name}>
                    <label>Cust. #{index + 1}</label>
                    <Field
                      name={`${name}.firstName`}
                      component="input"
                    />
                  </div>
                ))
              }
            </FieldArray>
          </form>
        )}
      </Form>
    )
  7. Implement a Higher Order Component (HOC) for React Final Form

    main

    React Final Form uses render props instead of Higher Order Components (HOCs) to avoid unnecessary bulk and complexity. If you need access to injected props (like initialize) within component lifecycle methods, you can wrap the Form component to create your own HOC.

    import { Form, Field } from 'react-final-form'
    
    class MyForm extends React.Component {
      componentDidMount() {
        const { initialize } = this.props // access to injected props
        ajax.fetch('/myData').then(data => initialize(data))
      }
    
      render() {
        return <form onSubmit={this.props.handleSubmit}>...some fields...</form>
      }
    }
    
    // 👇 THIS LINE IS THE HOC 👇
    export default props => <Form {...props} component={MyForm} />
  8. Implement form submission with `<Form/>`

    main

    To handle form submission, follow these three steps:

    1. Define onSubmit: Provide an onSubmit function to the <Form/> component. This function receives the form values and is only called if all validation passes.
    2. Render the form: Use the render or children prop to access the form state and functionality.
    3. Use handleSubmit: Use the handleSubmit function provided by the render props as the onSubmit handler for your HTML <form> element. handleSubmit automatically calls event.preventDefault() to prevent the default browser submission.

    Basic pattern:

    <Form onSubmit={onSubmit}>
      {props => (
        <form onSubmit={props.handleSubmit}>
          {/* ... fields ... */}
          <button type="submit">Submit</button>
        </form>
      )}
    </Form>
    <Form onSubmit={onSubmit}>
      {props => (
        <form onSubmit={props.handleSubmit}>
    
          ... fields go here...
    
          <button type="submit">Submit</button>
        </form>
      )}
    </Form>
  9. Trigger form submission from outside the form

    main

    If you need to trigger a form submission from a component located outside the <Form> tree, you can use one of the following methods:

    1. Use the HTML form attribute

    Assign an id to your <form> and reference that ID in the form attribute of your submit button.

    2. Dispatch a DOM event

    Use document.getElementById() to find the form and dispatch a submit event. Note: Do not use .submit(), as it will not trigger React's event handlers. You must use dispatchEvent with a cancelable, bubbling event.

    3. Use a Closure

    Capture the handleSubmit function provided by the render prop in a variable defined in an outer scope. To ensure the closure is correctly updated, call the function via an arrow function in the button's onClick handler.

    4. Redux Dead Drop

    If using Redux, you can implement the Redux Dead Drop pattern.

    // Method 1: HTML form attribute
    <button type="submit" form="myForm">Submit</button>
    <form id="myForm" onSubmit={handleSubmit}>
      ...fields go here...
    </form>
    
    // Method 2: Dispatching DOM event
    <button onClick={() => {
      document.getElementById('myForm')
      .dispatchEvent(new Event('submit', { cancelable: true, bubbles:true })) // ✅
    }}>Submit</button>
    
    // Method 3: Via Closure
    let submit
    return (
      <div>
        <button onClick={event => submit(event)}>Submit</button> // ✅
        <Form
          onSubmit={onSubmit}
          render={({ handleSubmit }) => {
            submit = handleSubmit
            return <form>...fields go here...</form>
          }}
        />
      </div>
    )