Final Form is a framework-agnostic, high-performance form state management library that uses subscription-based updates to minimize re-renders and optimize performance. It provides zero dependencies and a small bundle size (5.1k gzipped), making it ideal for building forms in any JavaScript framework or vanilla JS application.
npm install final-formimport {
createForm,
FORM_ERROR,
ARRAY_ERROR,
getIn,
setIn,
formSubscriptionItems,
fieldSubscriptionItems,
configOptions,
version
} from "final-form";For CommonJS:
const {
createForm,
FORM_ERROR,
ARRAY_ERROR,
getIn,
setIn,
formSubscriptionItems,
fieldSubscriptionItems,
configOptions,
version
} = require("final-form");import { createForm } from "final-form";
// Create a form instance
const form = createForm({
onSubmit: (values) => {
console.log("Form submitted:", values);
},
initialValues: {
firstName: "",
lastName: "",
email: "",
},
});
// Register a field and subscribe to its state
const unsubscribe = form.registerField(
"firstName",
(fieldState) => {
console.log("First name state:", fieldState);
},
{ value: true, error: true, touched: true }
);
// Change field value
form.change("firstName", "John");
// Subscribe to form state
const unsubscribeForm = form.subscribe(
(formState) => {
console.log("Form state:", formState);
},
{ values: true, errors: true, dirty: true }
);Final Form is built around several key architectural principles:
Core form creation functionality for initializing form instances with configuration and lifecycle management.
function createForm<FormValues, InitialFormValues>(
config: Config<FormValues, InitialFormValues>
): FormApi<FormValues, InitialFormValues>;
interface Config<FormValues, InitialFormValues> {
onSubmit: (
values: FormValues,
form: FormApi<FormValues, InitialFormValues>,
callback?: (errors?: SubmissionErrors) => void
) => SubmissionErrors | Promise<SubmissionErrors> | void;
debug?: DebugFunction<FormValues, InitialFormValues>;
destroyOnUnregister?: boolean;
initialValues?: InitialFormValues;
keepDirtyOnReinitialize?: boolean;
mutators?: { [key: string]: Mutator<FormValues, InitialFormValues> };
validate?: (values: FormValues) => ValidationErrors | Promise<ValidationErrors>;
validateOnBlur?: boolean;
callbackScheduler?: (callback: () => void) => void;
}Form and field state management with subscription-based updates for optimal performance.
interface FormApi<FormValues, InitialFormValues> {
getState(): FormState<FormValues, InitialFormValues>;
subscribe(
subscriber: FormSubscriber<FormValues, InitialFormValues>,
subscription: FormSubscription
): Unsubscribe;
subscribeFormState(
onChange: () => void,
subscription: FormSubscription
): Unsubscribe;
getFormSnapshot(): FormState<FormValues, InitialFormValues>;
batch(fn: () => void): void;
getRegisteredFields(): string[];
pauseValidation(): void;
resumeValidation(): void;
isValidationPaused(): boolean;
setConfig<K extends ConfigKey>(
name: K,
value: Config<FormValues, InitialFormValues>[K]
): void;
setCallbackScheduler(scheduler?: (callback: () => void) => void): void;
}
interface FormState<FormValues, InitialFormValues> {
active?: keyof FormValues;
dirty?: boolean;
dirtyFields?: { [key: string]: boolean };
dirtyFieldsSinceLastSubmit?: { [key: string]: boolean };
dirtySinceLastSubmit?: boolean;
error?: any;
errors?: ValidationErrors;
hasSubmitErrors?: boolean;
hasValidationErrors?: boolean;
initialValues?: InitialFormValues;
invalid?: boolean;
modified?: { [key: string]: boolean };
modifiedSinceLastSubmit?: boolean;
pristine?: boolean;
submitError?: any;
submitErrors?: SubmissionErrors;
submitFailed?: boolean;
submitSucceeded?: boolean;
submitting?: boolean;
touched?: { [key: string]: boolean };
valid?: boolean;
validating?: boolean;
values?: FormValues;
visited?: { [key: string]: boolean };
}Field registration and management system for dynamic form field handling with subscription capabilities.
interface FormApi<FormValues, InitialFormValues> {
registerField: RegisterField<FormValues>;
getFieldState<F extends keyof FormValues>(
field: F
): FieldState<FormValues[F]> | undefined;
subscribeFieldState<F extends keyof FormValues>(
name: F,
onChange: () => void,
subscription: FieldSubscription
): Unsubscribe;
getFieldSnapshot<F extends keyof FormValues>(
name: F
): FieldState<FormValues[F]> | undefined;
getRegisteredFields(): string[];
}
type RegisterField<FormValues> = <F extends keyof FormValues>(
name: F,
subscriber: FieldSubscriber<FormValues[F]>,
subscription: FieldSubscription,
config?: FieldConfig<FormValues[F]>
) => Unsubscribe;Comprehensive validation system supporting both synchronous and asynchronous validation at form and field levels.
interface Config<FormValues, InitialFormValues> {
validate?: (values: FormValues) => ValidationErrors | Promise<ValidationErrors>;
validateOnBlur?: boolean;
}
interface FieldConfig<FieldValue> {
getValidator?: GetFieldValidator<FieldValue>;
validateFields?: string[];
async?: boolean;
}
type FieldValidator<FieldValue> = (
value: FieldValue,
allValues: object,
meta?: FieldState<FieldValue>
) => any | Promise<any>;Form action methods for programmatic form manipulation including field changes, focus management, and form lifecycle.
interface FormApi<FormValues, InitialFormValues> {
change<F extends keyof FormValues>(name: F, value?: FormValues[F]): void;
blur(name: keyof FormValues): void;
focus(name: keyof FormValues): void;
initialize(
data: InitialFormValues | ((values: FormValues) => InitialFormValues)
): void;
reset(initialValues?: InitialFormValues): void;
restart(initialValues?: InitialFormValues): void;
resetFieldState(name: keyof FormValues): void;
submit(): Promise<FormValues | undefined> | undefined;
}Utility functions and constants for deep object manipulation and form state management.
function getIn(state: object, complexKey: string): any;
function setIn(
state: object,
key: string,
value: any,
destroyArrays?: boolean
): object;
const FORM_ERROR: string;
const ARRAY_ERROR: string;
const formSubscriptionItems: readonly string[];
const fieldSubscriptionItems: readonly string[];
const configOptions: ConfigKey[];
const version: string;interface FormSubscription {
active?: boolean;
dirty?: boolean;
dirtyFields?: boolean;
dirtyFieldsSinceLastSubmit?: boolean;
dirtySinceLastSubmit?: boolean;
modifiedSinceLastSubmit?: boolean;
error?: boolean;
errors?: boolean;
hasSubmitErrors?: boolean;
hasValidationErrors?: boolean;
initialValues?: boolean;
invalid?: boolean;
modified?: boolean;
pristine?: boolean;
submitError?: boolean;
submitErrors?: boolean;
submitFailed?: boolean;
submitting?: boolean;
submitSucceeded?: boolean;
touched?: boolean;
valid?: boolean;
validating?: boolean;
values?: boolean;
visited?: boolean;
}
interface FieldSubscription {
active?: boolean;
data?: boolean;
dirty?: boolean;
dirtySinceLastSubmit?: boolean;
error?: boolean;
initial?: boolean;
invalid?: boolean;
length?: boolean;
modified?: boolean;
modifiedSinceLastSubmit?: boolean;
pristine?: boolean;
submitError?: boolean;
submitFailed?: boolean;
submitSucceeded?: boolean;
submitting?: boolean;
touched?: boolean;
valid?: boolean;
validating?: boolean;
value?: boolean;
visited?: boolean;
}
interface FieldState<FieldValue> {
active?: boolean;
blur: () => void;
change: (value: FieldValue | undefined) => void;
data?: AnyObject;
dirty?: boolean;
dirtySinceLastSubmit?: boolean;
error?: any;
focus: () => void;
initial?: FieldValue;
invalid?: boolean;
length?: number;
modified?: boolean;
modifiedSinceLastSubmit?: boolean;
name: string;
pristine?: boolean;
submitError?: any;
submitFailed?: boolean;
submitSucceeded?: boolean;
submitting?: boolean;
touched?: boolean;
valid?: boolean;
validating?: boolean;
value?: FieldValue;
visited?: boolean;
}
type Unsubscribe = () => void;
type FieldSubscriber<FieldValue> = (state: FieldState<FieldValue>) => void;
type FormSubscriber<FormValues, InitialFormValues> = (
state: FormState<FormValues, InitialFormValues>
) => void;
type ValidationErrors = AnyObject | undefined;
type SubmissionErrors = AnyObject | undefined;
type AnyObject = { [key: string]: any };
type IsEqual = (a: any, b: any) => boolean;
type ConfigKey =
| "debug"
| "destroyOnUnregister"
| "initialValues"
| "keepDirtyOnReinitialize"
| "mutators"
| "onSubmit"
| "validate"
| "validateOnBlur"
| "callbackScheduler";
type DebugFunction<FormValues, InitialFormValues> = (
state: FormState<FormValues, InitialFormValues>,
fieldStates: { [key: string]: FieldState<any> }
) => void;
type Mutator<FormValues, InitialFormValues> = (
args: any[],
state: MutableState<FormValues, InitialFormValues>,
tools: Tools<FormValues, InitialFormValues>
) => any;
type GetFieldValidator<FieldValue = any> = () => FieldValidator<FieldValue> | undefined;
interface FieldConfig<FieldValue = any> {
afterSubmit?: () => void;
beforeSubmit?: () => void | false;
data?: any;
defaultValue?: any;
getValidator?: GetFieldValidator<FieldValue>;
initialValue?: any;
isEqual?: IsEqual;
silent?: boolean;
validateFields?: string[];
async?: boolean;
}
interface MutableState<FormValues, InitialFormValues> {
fieldSubscribers: { [key: string]: Subscribers<FieldState<any>> };
fields: { [key: string]: InternalFieldState<any> };
formState: InternalFormState<FormValues>;
lastFormState?: FormState<FormValues, InitialFormValues>;
}
interface Tools<FormValues, InitialFormValues> {
changeValue: ChangeValue<FormValues, InitialFormValues>;
getIn: GetIn;
renameField: RenameField<FormValues, InitialFormValues>;
resetFieldState: (name: string) => void;
setIn: SetIn;
shallowEqual: IsEqual;
}
type ChangeValue<FormValues, InitialFormValues> = (
state: MutableState<FormValues, InitialFormValues>,
name: string,
mutate: (value: any) => any
) => void;
type RenameField<FormValues, InitialFormValues> = (
state: MutableState<FormValues, InitialFormValues>,
from: string,
to: string
) => void;
type GetIn = (state: object, complexKey: string) => any;
type SetIn = (state: object, key: string, value: any, destroyArrays?: boolean) => object;
type Subscribers<T extends Object> = {
index: number;
entries: {
[key: number]: {
subscriber: Subscriber<T>;
subscription: Subscription;
notified: boolean;
};
};
};
type Subscription = { [key: string]: boolean | undefined };
type Subscriber<V> = (value: V) => void;
interface InternalFieldState<FieldValue = any> {
active: boolean;
afterSubmit?: () => void;
beforeSubmit?: () => void | false;
blur: () => void;
change: (value: any) => void;
data: AnyObject;
focus: () => void;
isEqual: IsEqual;
lastFieldState?: FieldState<FieldValue>;
length?: any;
modified: boolean;
modifiedSinceLastSubmit: boolean;
name: string;
touched: boolean;
validateFields?: string[];
validators: { [index: number]: GetFieldValidator<FieldValue> };
valid: boolean;
validating: boolean;
visited: boolean;
}
interface InternalFormState<FormValues = Record<string, any>> {
active?: string;
asyncErrors: AnyObject;
dirtySinceLastSubmit: boolean;
modifiedSinceLastSubmit: boolean;
error?: any;
errors: ValidationErrors;
initialValues?: AnyObject;
lastSubmittedValues?: AnyObject;
pristine: boolean;
resetWhileSubmitting: boolean;
submitError?: any;
submitErrors?: AnyObject;
submitFailed: boolean;
submitSucceeded: boolean;
submitting: boolean;
valid: boolean;
validating: number;
values: FormValues;
}