Field registration and management system for dynamic form field handling with subscription capabilities and lifecycle management.
Register a field with the form and subscribe to its state changes.
type RegisterField<FormValues = Record<string, any>> = <F extends keyof FormValues>(
name: F,
subscriber: FieldSubscriber<FormValues[F]>,
subscription: FieldSubscription,
config?: FieldConfig<FormValues[F]>
) => Unsubscribe;
interface FormApi<FormValues, InitialFormValues> {
/** Register a field with subscription and optional configuration */
registerField: RegisterField<FormValues>;
}Usage Examples:
import { createForm } from "final-form";
const form = createForm({
onSubmit: (values) => console.log(values)
});
// Basic field registration
const unsubscribe = form.registerField(
'firstName',
(fieldState) => {
console.log('First name:', fieldState.value);
console.log('Has error:', fieldState.error);
console.log('Is touched:', fieldState.touched);
},
{ value: true, error: true, touched: true }
);
// Field with configuration
const unsubscribeEmail = form.registerField(
'email',
(fieldState) => {
updateEmailInput(fieldState);
},
{ value: true, error: true, touched: true, valid: true },
{
initialValue: '',
validateFields: ['confirmEmail'], // Validate confirmEmail when email changes
getValidator: () => (value) => {
if (!value) return 'Email is required';
if (!/\S+@\S+\.\S+/.test(value)) return 'Invalid email format';
}
}
);
// Cleanup when component unmounts
return () => {
unsubscribe();
unsubscribeEmail();
};Methods for accessing field state without subscribing to changes.
interface FormApi<FormValues, InitialFormValues> {
/** Get current state of a specific field */
getFieldState<F extends keyof FormValues>(
field: F
): FieldState<FormValues[F]> | undefined;
/** Subscribe to field state changes with callback approach */
subscribeFieldState<F extends keyof FormValues>(
name: F,
onChange: () => void,
subscription: FieldSubscription
): Unsubscribe;
/** Get a snapshot of current field state */
getFieldSnapshot<F extends keyof FormValues>(
name: F
): FieldState<FormValues[F]> | undefined;
/** Get names of all currently registered fields */
getRegisteredFields(): string[];
}Usage Examples:
// Get current field state
const emailState = form.getFieldState('email');
if (emailState?.error) {
showFieldError('email', emailState.error);
}
// Callback-based field subscription
const unsubscribeCallback = form.subscribeFieldState(
'firstName',
() => {
const snapshot = form.getFieldSnapshot('firstName');
console.log('First name changed:', snapshot?.value);
},
{ value: true }
);
// Get all registered fields
const registeredFields = form.getRegisteredFields();
console.log('Registered fields:', registeredFields);Complete field state object with all available properties and methods.
interface FieldState<FieldValue = any> {
/** Whether field is currently active (focused) */
active?: boolean;
/** Function to blur the field */
blur: () => void;
/** Function to change the field value */
change: (value: FieldValue | undefined) => void;
/** Custom data attached to the field */
data?: AnyObject;
/** Whether field value differs from initial value */
dirty?: boolean;
/** Whether field has been modified since last submission */
dirtySinceLastSubmit?: boolean;
/** Field validation error */
error?: any;
/** Function to focus the field */
focus: () => void;
/** Initial value of the field */
initial?: FieldValue;
/** Whether field has validation errors */
invalid?: boolean;
/** Array length (for array fields) */
length?: number;
/** Whether field has been modified from initial value */
modified?: boolean;
/** Whether field has been modified since last submission */
modifiedSinceLastSubmit?: boolean;
/** Field name */
name: string;
/** Whether field is in its initial, unmodified state */
pristine?: boolean;
/** Field submission error */
submitError?: any;
/** Whether last submission failed for this field */
submitFailed?: boolean;
/** Whether last submission succeeded */
submitSucceeded?: boolean;
/** Whether form is currently being submitted */
submitting?: boolean;
/** Whether field has been touched (focused and blurred) */
touched?: boolean;
/** Whether field passes validation */
valid?: boolean;
/** Whether field is currently being validated */
validating?: boolean;
/** Current field value */
value?: FieldValue;
/** Whether field has been visited (focused) */
visited?: boolean;
}Subscription object for controlling which field state properties trigger updates.
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;
}Configuration options when registering a field.
interface FieldConfig<FieldValue = any> {
/** Callback executed after form submission */
afterSubmit?: () => void;
/** Callback executed before form submission, can prevent submission by returning false */
beforeSubmit?: () => void | false;
/** Custom data to attach to the field */
data?: any;
/** Default value when field value is undefined */
defaultValue?: any;
/** Function that returns a validator for this field */
getValidator?: GetFieldValidator<FieldValue>;
/** Initial value for the field */
initialValue?: any;
/** Custom equality function for determining if field value changed */
isEqual?: IsEqual;
/** Whether field changes should trigger notifications (default: false) */
silent?: boolean;
/** Names of other fields to validate when this field changes */
validateFields?: string[];
/** Whether field validation is asynchronous */
async?: boolean;
}Field Configuration Examples:
// Field with custom validator
form.registerField(
'age',
(fieldState) => updateAgeField(fieldState),
{ value: true, error: true },
{
getValidator: () => (value) => {
if (!value) return 'Age is required';
if (value < 18) return 'Must be 18 or older';
if (value > 120) return 'Invalid age';
}
}
);
// Field with cross-field validation
form.registerField(
'password',
(fieldState) => updatePasswordField(fieldState),
{ value: true, error: true },
{
validateFields: ['confirmPassword'], // Re-validate confirmPassword when password changes
getValidator: () => (value) => {
if (!value) return 'Password is required';
if (value.length < 8) return 'Password must be at least 8 characters';
}
}
);
// Field with lifecycle callbacks
form.registerField(
'specialField',
(fieldState) => updateSpecialField(fieldState),
{ value: true },
{
beforeSubmit: () => {
// Perform validation or cleanup before submission
const isValid = performSpecialValidation();
return isValid; // Return false to prevent submission
},
afterSubmit: () => {
// Cleanup or reset state after submission
resetSpecialFieldState();
}
}
);
// Field with custom equality
form.registerField(
'objectField',
(fieldState) => updateObjectField(fieldState),
{ value: true },
{
isEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b), // Deep comparison
initialValue: { nested: { value: 'default' } }
}
);Type definition for field state subscriber functions.
type FieldSubscriber<FieldValue = any> = (state: FieldState<FieldValue>) => void;
type GetFieldValidator<FieldValue = any> = () => FieldValidator<FieldValue> | undefined;
type FieldValidator<FieldValue = any> = (
value: FieldValue,
allValues: object,
meta?: FieldState<FieldValue>
) => any | Promise<any>;List of all available field subscription properties.
const fieldSubscriptionItems: readonly string[];The fieldSubscriptionItems array contains: ["active", "data", "dirty", "dirtySinceLastSubmit", "error", "initial", "invalid", "length", "modified", "modifiedSinceLastSubmit", "pristine", "submitError", "submitFailed", "submitSucceeded", "submitting", "touched", "valid", "value", "visited", "validating"]
Examples of managing fields dynamically:
// Dynamic field registration based on conditions
const conditionalField = (fieldName: string, shouldRegister: boolean) => {
if (shouldRegister) {
return form.registerField(
fieldName,
(fieldState) => console.log(`${fieldName}:`, fieldState.value),
{ value: true }
);
}
return () => {}; // No-op unsubscribe function
};
// Array field management
const registerArrayField = (arrayName: string, index: number) => {
const fieldName = `${arrayName}[${index}]`;
return form.registerField(
fieldName,
(fieldState) => updateArrayItem(arrayName, index, fieldState),
{ value: true, error: true },
{
getValidator: () => (value) => validateArrayItem(value, index)
}
);
};
// Bulk field registration
const registerMultipleFields = (fieldNames: string[]) => {
const unsubscribeFunctions = fieldNames.map(fieldName =>
form.registerField(
fieldName,
(fieldState) => updateField(fieldName, fieldState),
{ value: true, error: true, touched: true }
)
);
// Return cleanup function
return () => {
unsubscribeFunctions.forEach(unsubscribe => unsubscribe());
};
};