{"id":10893,"library":"final-form","title":"Final Form","description":"Final Form is a high-performance, framework-agnostic library for managing form state in JavaScript applications. It provides a subscription-based model, meaning components only re-render when the specific pieces of state they subscribe to change, leading to optimized performance. The current stable version is 5.0.0, which notably converted the entire codebase from Flow to TypeScript, enhancing developer experience for TypeScript users. While core API stability is maintained across minor versions, major versions are bumped cautiously, as seen with v5.0.0, to reflect significant internal changes. Key differentiators include its zero-dependency footprint, small bundle size (around 5.1kB gzipped), and its explicit opt-in subscription model, giving developers fine-grained control over re-renders, making it suitable for complex form interactions across various UI frameworks.","status":"active","version":"5.0.0","language":"javascript","source_language":"en","source_url":"https://github.com/final-form/final-form","tags":["javascript","typescript"],"install":[{"cmd":"npm install final-form","lang":"bash","label":"npm"},{"cmd":"yarn add final-form","lang":"bash","label":"yarn"},{"cmd":"pnpm add final-form","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"ESM is the recommended import style. CommonJS `require` is generally discouraged in modern applications and may lead to issues with bundlers or tree-shaking.","wrong":"const createForm = require('final-form')","symbol":"createForm","correct":"import { createForm } from 'final-form'"},{"note":"FormApi is a TypeScript type representing the form instance. It should be imported as a type for type-checking purposes only. Importing it as a value will result in a runtime error or unnecessary bundle size.","wrong":"import { FormApi } from 'final-form'","symbol":"FormApi","correct":"import type { FormApi } from 'final-form'"},{"note":"FieldState is a TypeScript type for the state of an individual field. Like FormApi, it should be imported as a type.","wrong":"import { FieldState } from 'final-form'","symbol":"FieldState","correct":"import type { FieldState } from 'final-form'"}],"quickstart":{"code":"import { createForm } from 'final-form';\n\ninterface MyFormData {\n  firstName: string;\n  lastName: string;\n  age: number;\n}\n\nconst form = createForm<MyFormData>({\n  initialValues: { firstName: 'John', lastName: 'Doe', age: 30 },\n  onSubmit: async (values) => {\n    await new Promise(resolve => setTimeout(resolve, 500));\n    console.log('Form submitted with values:', values);\n  },\n  validate: (values) => {\n    const errors: Partial<MyFormData> = {};\n    if (!values.firstName) {\n      errors.firstName = 'Required';\n    }\n    if (!values.lastName) {\n      errors.lastName = 'Required';\n    }\n    if (values.age < 18) {\n      errors.age = 'Must be 18 or older';\n    }\n    return errors;\n  },\n});\n\nconst unsubscribe = form.subscribe((formState) => {\n  console.log('Form State Changed:', {\n    values: formState.values,\n    errors: formState.errors,\n    valid: formState.valid,\n    dirty: formState.dirty,\n    submitting: formState.submitting,\n  });\n}, { values: true, errors: true, valid: true, dirty: true, submitting: true });\n\n// Simulate user input\nform.change('firstName', 'Jane');\nform.change('age', 17);\n\n// Simulate form submission\nform.submit();\n\n// Cleanup (in a real app, this would be on unmount)\n// unsubscribe();\n","lang":"typescript","description":"This quickstart demonstrates how to create a basic form instance with initial values, onSubmit and validation logic, and subscribe to form state changes using `createForm` from `final-form`."},"warnings":[{"fix":"Review TypeScript definitions in your project, especially if you had custom type augmentations for previous Flow-typed versions. Ensure your `tsconfig.json` is compatible and rebuild your application.","message":"Version 5.0.0 converted the entire library from Flow to TypeScript. While no *intentional* API breaking changes were introduced, developers relying on Flow types or intricate type inference might experience minor issues or require adjustments to their TypeScript configurations. Always test thoroughly when upgrading.","severity":"breaking","affected_versions":">=5.0.0"},{"fix":"Always explicitly define which form state properties (e.g., `{ values: true, errors: true, dirty: true }`) your component needs to re-render. Avoid subscribing to `everything: true` unless absolutely necessary for debugging or specific scenarios.","message":"Final Form's subscription model is powerful but requires careful selection of subscribed state. Subscribing to too much state can negate performance benefits, while subscribing to too little might cause components not to re-render when expected.","severity":"gotcha","affected_versions":">=4.0.0"},{"fix":"Ensure your async validation logic correctly returns promises and handles their resolution/rejection. Review the official documentation on `asyncValidate` and consider debouncing your validation function for better user experience and to prevent validation 'races'.","message":"Asynchronous validation in Final Form can sometimes lead to unexpected behavior, such as errors not clearing or validations running at incorrect times, especially if not handled correctly with debouncing or when field values change rapidly.","severity":"gotcha","affected_versions":">=4.20.0"},{"fix":"If using versions 4.20.7 or 4.20.8, explicitly handle `allValues` as potentially undefined or ensure your validator functions are robust to its presence. For optimal type safety, upgrade to >=4.20.9 where `allValues` is consistently required or check documentation for current behavior.","message":"The `allValues` argument for `FieldValidator` was made optional in v4.20.7, but this change was reverted in v4.20.9. This inconsistency might cause type errors or unexpected runtime behavior if your validation functions rely on its optionality or presence across these specific versions.","severity":"gotcha","affected_versions":"4.20.7 - 4.20.8"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Ensure that the second argument to `form.subscribe` is a `FormSubscription` object, where keys are state properties and values are booleans indicating subscription interest, e.g., `{ values: true, errors: true }`.","cause":"Attempting to pass a partial FormState object as a subscription parameter instead of a FormSubscription object.","error":"TS2345: Argument of type 'Partial<FormState<T>>' is not assignable to parameter of type 'FormSubscription'."},{"fix":"Use ES module import syntax: `import { createForm } from 'final-form';`. If in a CommonJS-only environment (e.g., older Node.js scripts), ensure your bundler or environment correctly transpiles ESM or use dynamic import if supported.","cause":"Incorrect CommonJS `require` syntax or mixing CommonJS with ESM in a module that expects ESM imports.","error":"TypeError: createForm is not a function"},{"fix":"Access form values via `formState.values.someField` and errors via `formState.errors.someField`. `FormState` itself contains metadata about the form, not the field values directly.","cause":"Directly accessing field values on the `FormState` object rather than through the `formState.values` property.","error":"Property 'someField' does not exist on type 'FormState<MyFormData>'. Did you mean 'values'?"}],"ecosystem":"npm"}