Comprehensive form and field state management with subscription-based updates for optimal performance and minimal re-renders.
Methods for accessing and subscribing to form state changes.
interface FormApi<FormValues, InitialFormValues> {
/** Get current form state synchronously */
getState(): FormState<FormValues, InitialFormValues>;
/** Subscribe to form state changes with selective subscription */
subscribe(
subscriber: FormSubscriber<FormValues, InitialFormValues>,
subscription: FormSubscription
): Unsubscribe;
/** Subscribe to form state with callback-based approach */
subscribeFormState(
onChange: () => void,
subscription: FormSubscription
): Unsubscribe;
/** Get a snapshot of current form state */
getFormSnapshot(): FormState<FormValues, InitialFormValues>;
/** Batch multiple operations to prevent multiple notifications */
batch(fn: () => void): void;
}Usage Examples:
import { createForm } from "final-form";
const form = createForm({
onSubmit: (values) => console.log(values)
});
// Get current state
const currentState = form.getState();
console.log('Form is valid:', currentState.valid);
// Subscribe to specific form state properties
const unsubscribe = form.subscribe(
(formState) => {
console.log('Values changed:', formState.values);
console.log('Form is dirty:', formState.dirty);
},
{ values: true, dirty: true } // Only get notified of these changes
);
// Callback-based subscription
const unsubscribeCallback = form.subscribeFormState(
() => {
const snapshot = form.getFormSnapshot();
console.log('Form state changed:', snapshot);
},
{ valid: true, submitting: true }
);
// Batch operations to prevent multiple notifications
form.batch(() => {
form.change('firstName', 'John');
form.change('lastName', 'Doe');
form.change('email', 'john@example.com');
// Only one notification will be sent after all changes
});Complete form state object with all available properties.
interface FormState<
FormValues = Record<string, any>,
InitialFormValues extends Partial<FormValues> = Partial<FormValues>
> {
/** Currently active (focused) field name */
active?: keyof FormValues;
/** Whether any field has been modified from initial values */
dirty?: boolean;
/** Object indicating which specific fields are dirty */
dirtyFields?: { [key: string]: boolean };
/** Which fields have been modified since last submit */
dirtyFieldsSinceLastSubmit?: { [key: string]: boolean };
/** Whether form has been modified since last submission */
dirtySinceLastSubmit?: boolean;
/** Form-level error (when using FORM_ERROR) */
error?: any;
/** Field-level validation errors */
errors?: ValidationErrors;
/** Whether form has submission errors */
hasSubmitErrors?: boolean;
/** Whether form has validation errors */
hasValidationErrors?: boolean;
/** Initial values provided at form creation */
initialValues?: InitialFormValues;
/** Whether form has any validation errors */
invalid?: boolean;
/** Object indicating which fields have been modified */
modified?: { [key: string]: boolean };
/** Whether form has been modified since last submission */
modifiedSinceLastSubmit?: boolean;
/** Whether form is in its initial, unmodified state */
pristine?: boolean;
/** Form-level submission error */
submitError?: any;
/** Field-level submission errors */
submitErrors?: SubmissionErrors;
/** Whether last submission attempt failed */
submitFailed?: boolean;
/** Whether last submission attempt succeeded */
submitSucceeded?: boolean;
/** Whether form is currently being submitted */
submitting?: boolean;
/** Object indicating which fields have been touched */
touched?: { [key: string]: boolean };
/** Whether form passes all validation */
valid?: boolean;
/** Whether form is currently being validated */
validating?: boolean;
/** Current form values */
values?: FormValues;
/** Object indicating which fields have been visited */
visited?: { [key: string]: boolean };
}Subscription object for controlling which form state properties trigger updates.
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;
}Subscription Examples:
// Subscribe only to validation state changes
form.subscribe(
(state) => {
if (state.invalid) {
showErrorMessage('Please fix validation errors');
}
},
{ valid: true, invalid: true, errors: true }
);
// Subscribe to submission state
form.subscribe(
(state) => {
if (state.submitting) {
showLoadingSpinner();
} else if (state.submitSucceeded) {
showSuccessMessage();
} else if (state.submitFailed) {
showErrorMessage('Submission failed');
}
},
{ submitting: true, submitSucceeded: true, submitFailed: true }
);
// Subscribe to form dirty state for unsaved changes warning
form.subscribe(
(state) => {
if (state.dirty) {
enableUnsavedChangesWarning();
} else {
disableUnsavedChangesWarning();
}
},
{ dirty: true, pristine: true }
);Type definitions for form state subscriber functions.
type FormSubscriber<
FormValues = Record<string, any>,
InitialFormValues extends Partial<FormValues> = Partial<FormValues>
> = (state: FormState<FormValues, InitialFormValues>) => void;
type Unsubscribe = () => void;List of all available form subscription properties for reference.
const formSubscriptionItems: readonly string[];The formSubscriptionItems array contains: ["active", "dirty", "dirtyFields", "dirtyFieldsSinceLastSubmit", "dirtySinceLastSubmit", "error", "errors", "hasSubmitErrors", "hasValidationErrors", "initialValues", "invalid", "modified", "modifiedSinceLastSubmit", "pristine", "submitting", "submitError", "submitErrors", "submitFailed", "submitSucceeded", "touched", "valid", "validating", "values", "visited"]
Usage Example:
import { formSubscriptionItems } from "final-form";
// Create a subscription that includes all properties
const fullSubscription = formSubscriptionItems.reduce((acc, item) => {
acc[item] = true;
return acc;
}, {});
// Subscribe to all form state changes
form.subscribe(
(state) => console.log('Full form state:', state),
fullSubscription
);The subscription system is designed for optimal performance:
// ❌ Bad: Subscribing to all changes causes unnecessary re-renders
form.subscribe(
(state) => updateUI(state),
{
values: true,
errors: true,
dirty: true,
submitting: true,
valid: true,
// ... many more properties
}
);
// ✅ Good: Subscribe only to properties you actually use
form.subscribe(
(state) => {
if (state.submitting) {
showSpinner();
}
},
{ submitting: true } // Only get notified when submitting changes
);
// ✅ Good: Separate subscriptions for different UI concerns
form.subscribe(
(state) => updateFormValues(state.values),
{ values: true }
);
form.subscribe(
(state) => updateValidationErrors(state.errors),
{ errors: true }
);