High performance subscription-based form state management for React
npx @tessl/cli install tessl/npm-react-final-form@7.0.0React Final Form is a high-performance, subscription-based form state management library for React applications. Built as a thin wrapper around Final Form, it provides efficient form handling through the Observer pattern with zero dependencies that affect bundle size and a tiny 3.0k gzipped footprint.
npm install react-final-formimport {
Form,
Field,
FormSpy,
useForm,
useField,
useFormState,
withTypes,
version,
all
} from "react-final-form";
// Import types from react-final-form
import type {
FormProps,
FieldProps,
FormSpyProps,
FieldRenderProps,
FormRenderProps,
UseFieldConfig,
UseFormStateParams,
ReactContext,
FormSpyPropsWithForm,
FieldInputProps,
RenderableProps,
SubmitEvent,
UseFieldAutoConfig
} from "react-final-form";
// Import types from final-form (peer dependency)
import type {
FormApi,
FormState,
FieldState,
FormSubscription,
FieldSubscription,
Config,
Decorator,
FieldValidator
} from "final-form";For CommonJS:
const {
Form,
Field,
FormSpy,
useForm,
useField,
useFormState,
withTypes,
version,
all
} = require("react-final-form");import React from "react";
import { Form, Field } from "react-final-form";
interface FormValues {
firstName: string;
lastName: string;
email: string;
}
const onSubmit = (values: FormValues) => {
console.log(values);
};
function MyForm() {
return (
<Form<FormValues>
onSubmit={onSubmit}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}>
<Field name="firstName">
{({ input, meta }) => (
<div>
<label>First Name</label>
<input {...input} type="text" placeholder="First Name" />
{meta.error && meta.touched && <span>{meta.error}</span>}
</div>
)}
</Field>
<Field name="email">
{({ input, meta }) => (
<div>
<label>Email</label>
<input {...input} type="email" placeholder="Email" />
{meta.error && meta.touched && <span>{meta.error}</span>}
</div>
)}
</Field>
<button type="submit" disabled={submitting}>
Submit
</button>
<button
type="button"
onClick={form.reset}
disabled={submitting || pristine}
>
Reset
</button>
</form>
)}
/>
);
}React Final Form is built around several key components:
Core form wrapper component that provides form context, state management, and submission handling with efficient subscription-based updates.
const Form: <FormValues = Record<string, any>>(
props: FormProps<FormValues>
) => React.ReactElement;
interface FormProps<FormValues = Record<string, any>>
extends Config<FormValues>,
RenderableProps<FormRenderProps<FormValues>> {
subscription?: FormSubscription;
decorators?: Decorator<FormValues>[];
form?: FormApi<FormValues>;
initialValuesEqual?: (
a?: Record<string, any>,
b?: Record<string, any>
) => boolean;
}
interface FormRenderProps<FormValues = Record<string, any>>
extends FormState<FormValues> {
handleSubmit: (
event?: SubmitEvent
) => Promise<Record<string, any> | undefined> | undefined;
form: FormApi<FormValues>;
}Individual field components with subscription-based state management, validation, formatting, and parsing capabilities.
const Field: <
FieldValue = any,
T extends HTMLElement = HTMLElement,
FormValues = Record<string, any>
>(
props: FieldProps<FieldValue, T, FormValues>
) => React.ReactElement;
interface FieldProps<
FieldValue = any,
T extends HTMLElement = HTMLElement,
FormValues = Record<string, any>
> extends UseFieldConfig,
Omit<RenderableProps<FieldRenderProps<FieldValue, T>>, "children"> {
name: string;
children?: RenderableProps<FieldRenderProps<FieldValue, T>>["children"];
[key: string]: any;
}
interface FieldRenderProps<
FieldValue = any,
T extends HTMLElement = HTMLElement,
FormValues = any
> {
input: FieldInputProps<FieldValue, T>;
meta: {
active?: boolean;
data?: Record<string, any>;
dirty?: boolean;
dirtySinceLastSubmit?: boolean;
error?: any;
initial?: any;
invalid?: boolean;
length?: number;
modified?: boolean;
modifiedSinceLastSubmit?: boolean;
pristine?: boolean;
submitError?: any;
submitFailed?: boolean;
submitSucceeded?: boolean;
submitting?: boolean;
touched?: boolean;
valid?: boolean;
validating?: boolean;
visited?: boolean;
};
}FormSpy component for observing form state changes without rendering form fields, perfect for external components that need form state updates.
const FormSpy: <FormValues = Record<string, any>>(
props: FormSpyProps<FormValues>
) => React.ReactElement;
interface FormSpyProps<FormValues = Record<string, any>>
extends UseFormStateParams<FormValues>,
RenderableProps<FormSpyRenderProps<FormValues>> {}
interface FormSpyRenderProps<FormValues = Record<string, any>>
extends FormState<FormValues> {
form: FormApi<FormValues>;
}Modern React hooks for accessing form context, field state, and form state with customizable subscriptions and full TypeScript support.
function useForm<FormValues = Record<string, any>>(
componentName?: string
): FormApi<FormValues>;
function useField<
FieldValue = any,
T extends HTMLElement = HTMLElement,
FormValues = Record<string, any>
>(
name: string,
config?: UseFieldConfig
): FieldRenderProps<FieldValue, T, FormValues>;
function useFormState<FormValues = Record<string, any>>(
params?: UseFormStateParams<FormValues>
): FormState<FormValues>;Utility functions and type helpers for enhanced TypeScript support with strongly typed form and field components.
function withTypes<FormValues = Record<string, any>>(): {
Form: React.ComponentType<FormProps<FormValues>>;
FormSpy: React.ComponentType<FormSpyProps<FormValues>>;
};
const version: string;
const all: FormSubscription;interface FieldInputProps<
FieldValue = any,
T extends HTMLElement = HTMLElement
> {
name: string;
onBlur: (event?: React.FocusEvent<T>) => void;
onChange: (event: React.ChangeEvent<T> | any) => void;
onFocus: (event?: React.FocusEvent<T>) => void;
value: FieldValue;
checked?: boolean;
multiple?: boolean;
type?: string;
}
interface RenderableProps<T> {
component?: React.ComponentType<any> | SupportedInputs;
children?: ((props: T) => React.ReactNode) | React.ReactNode;
render?: (props: T) => React.ReactNode;
}
interface SubmitEvent {
preventDefault?: () => void;
stopPropagation?: () => void;
}
interface UseFormStateParams<FormValues = Record<string, any>> {
onChange?: (formState: FormState<FormValues>) => void;
subscription?: FormSubscription;
}
interface UseFieldConfig extends UseFieldAutoConfig {
subscription?: FieldSubscription;
}
interface UseFieldAutoConfig {
afterSubmit?: () => void;
allowNull?: boolean;
beforeSubmit?: () => void | false;
component?: RenderableProps<any>["component"];
data?: Record<string, any>;
defaultValue?: any;
format?: (value: any, name: string) => any;
formatOnBlur?: boolean;
initialValue?: any;
isEqual?: (a: any, b: any) => boolean;
multiple?: boolean;
parse?: (value: any, name: string) => any;
type?: string;
validate?: FieldValidator<any>;
validateFields?: string[];
value?: any;
}
interface ReactContext<FormValues = Record<string, any>> {
reactFinalForm: FormApi<FormValues>;
}
interface FormSpyPropsWithForm<FormValues = Record<string, any>>
extends FormSpyProps<FormValues> {
reactFinalForm: FormApi<FormValues>;
}
type SupportedInputs = "input" | "select" | "textarea";