Form action methods for programmatic form manipulation including field changes, focus management, form lifecycle operations, and configuration updates.
Methods for programmatically changing field values and managing field state.
interface FormApi<FormValues, InitialFormValues> {
/** Change the value of a specific field */
change<F extends keyof FormValues>(name: F, value?: FormValues[F]): void;
/** Reset a specific field to its initial state */
resetFieldState(name: keyof FormValues): void;
}Usage Examples:
import { createForm } from "final-form";
const form = createForm({
onSubmit: (values) => console.log(values),
initialValues: {
firstName: '',
lastName: '',
email: '',
age: null
}
});
// Change field values programmatically
form.change('firstName', 'John');
form.change('lastName', 'Doe');
form.change('email', 'john.doe@example.com');
form.change('age', 30);
// Clear a field value
form.change('firstName', undefined);
// Reset specific field to initial state
form.resetFieldState('email'); // Resets value, errors, touched, etc.
// Bulk value changes using batch
form.batch(() => {
form.change('firstName', 'Jane');
form.change('lastName', 'Smith');
form.change('email', 'jane.smith@example.com');
}); // Only triggers one update notificationMethods for programmatically managing field focus states.
interface FormApi<FormValues, InitialFormValues> {
/** Focus a specific field */
focus(name: keyof FormValues): void;
/** Blur a specific field */
blur(name: keyof FormValues): void;
}Usage Examples:
// Focus management for user experience
const handleNextButton = () => {
const state = form.getState();
if (!state.values?.firstName) {
form.focus('firstName');
return;
}
if (!state.values?.lastName) {
form.focus('lastName');
return;
}
if (!state.values?.email) {
form.focus('email');
return;
}
// All required fields filled, proceed
form.submit();
};
// Auto-focus first field with error
const focusFirstError = () => {
const state = form.getState();
if (state.errors) {
const firstErrorField = Object.keys(state.errors)[0];
if (firstErrorField) {
form.focus(firstErrorField);
}
}
};
// Blur field to trigger validation
const validateAndBlur = (fieldName) => {
form.blur(fieldName); // This will trigger validation if validateOnBlur is true
};
// Custom focus flow
const customFocusFlow = {
next: (currentField) => {
const fieldOrder = ['firstName', 'lastName', 'email', 'phone'];
const currentIndex = fieldOrder.indexOf(currentField);
const nextField = fieldOrder[currentIndex + 1];
if (nextField) {
form.focus(nextField);
}
},
previous: (currentField) => {
const fieldOrder = ['firstName', 'lastName', 'email', 'phone'];
const currentIndex = fieldOrder.indexOf(currentField);
const previousField = fieldOrder[currentIndex - 1];
if (previousField) {
form.focus(previousField);
}
}
};Methods for managing the form's lifecycle including initialization, reset, and restart operations.
interface FormApi<FormValues, InitialFormValues> {
/** Initialize form with new values */
initialize(
data: InitialFormValues | ((values: FormValues) => InitialFormValues)
): void;
/** Reset form to initial values */
reset(initialValues?: InitialFormValues): void;
/** Restart form (reset + clear history) */
restart(initialValues?: InitialFormValues): void;
}Usage Examples:
// Initialize form with user data
const loadUserData = async (userId) => {
const userData = await fetchUserData(userId);
form.initialize({
firstName: userData.firstName,
lastName: userData.lastName,
email: userData.email,
dateOfBirth: userData.dateOfBirth
});
};
// Initialize with computed values
form.initialize((currentValues) => ({
...currentValues,
fullName: `${currentValues.firstName} ${currentValues.lastName}`,
slug: generateSlug(currentValues.firstName, currentValues.lastName)
}));
// Reset form to original state
const handleReset = () => {
if (confirm('Are you sure you want to reset all changes?')) {
form.reset(); // Reset to original initialValues
}
};
// Reset with new initial values
const handleResetWithNewData = () => {
form.reset({
firstName: 'New',
lastName: 'Values',
email: 'new@example.com'
});
};
// Restart form (clears all history including dirty, touched, etc.)
const handleRestart = () => {
form.restart(); // Complete fresh start
};
// Restart with new initial values
const handleRestartWithNewValues = () => {
form.restart({
firstName: '',
lastName: '',
email: '',
preferences: {
theme: 'dark',
notifications: true
}
});
};Methods for handling form submission programmatically.
interface FormApi<FormValues, InitialFormValues> {
/** Submit the form programmatically */
submit(): Promise<FormValues | undefined> | undefined;
}Usage Examples:
// Programmatic submission
const handleSubmitButton = async () => {
try {
const result = await form.submit();
if (result) {
console.log('Form submitted successfully:', result);
showSuccessMessage('Form submitted!');
} else {
console.log('Form submission failed due to validation errors');
focusFirstError();
}
} catch (error) {
console.error('Submission error:', error);
showErrorMessage('Submission failed');
}
};
// Conditional submission
const handleConditionalSubmit = async () => {
const state = form.getState();
if (!state.valid) {
showErrorMessage('Please fix validation errors before submitting');
focusFirstError();
return;
}
if (state.submitting) {
console.log('Form is already being submitted');
return;
}
await form.submit();
};
// Auto-save functionality
let autoSaveTimer;
const setupAutoSave = () => {
form.subscribe(
(state) => {
if (state.dirty && state.valid) {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(() => {
form.submit();
}, 2000); // Auto-save after 2 seconds of inactivity
}
},
{ dirty: true, valid: true }
);
};Methods for updating form configuration at runtime.
interface FormApi<FormValues, InitialFormValues> {
/** Update a specific configuration option */
setConfig<K extends ConfigKey>(
name: K,
value: Config<FormValues, InitialFormValues>[K]
): void;
/** Set custom callback scheduler */
setCallbackScheduler(scheduler?: (callback: () => void) => void): void;
}
type ConfigKey =
| "debug"
| "destroyOnUnregister"
| "initialValues"
| "keepDirtyOnReinitialize"
| "mutators"
| "onSubmit"
| "validate"
| "validateOnBlur"
| "callbackScheduler";Usage Examples:
// Update validation function at runtime
const updateValidation = (strictMode) => {
if (strictMode) {
form.setConfig('validate', (values) => {
const errors = {};
// Strict validation rules
if (!values.firstName?.trim()) errors.firstName = 'First name is required';
if (!values.lastName?.trim()) errors.lastName = 'Last name is required';
if (!values.email?.trim()) errors.email = 'Email is required';
if (!values.phone?.trim()) errors.phone = 'Phone is required';
return errors;
});
} else {
form.setConfig('validate', (values) => {
const errors = {};
// Lenient validation rules
if (!values.email?.trim()) errors.email = 'Email is required';
return errors;
});
}
};
// Toggle validation on blur
const toggleValidateOnBlur = (enabled) => {
form.setConfig('validateOnBlur', enabled);
};
// Update submit handler
const updateSubmitHandler = (newHandler) => {
form.setConfig('onSubmit', newHandler);
};
// Toggle debug mode
const toggleDebugMode = (enabled) => {
if (enabled) {
form.setConfig('debug', (formState, fieldStates) => {
console.log('Form State:', formState);
console.log('Field States:', fieldStates);
});
} else {
form.setConfig('debug', undefined);
}
};
// Custom callback scheduler for React 18 concurrent features
const setupReactScheduler = () => {
form.setCallbackScheduler((callback) => {
// Use React's scheduler for better performance
if (typeof window !== 'undefined' && window.scheduler?.postTask) {
window.scheduler.postTask(callback, { priority: 'user-blocking' });
} else {
// Fallback to immediate execution
callback();
}
});
};
// Remove custom scheduler
const removeCustomScheduler = () => {
form.setCallbackScheduler(undefined);
};Methods for batching multiple operations to prevent excessive notifications.
interface FormApi<FormValues, InitialFormValues> {
/** Batch multiple operations to prevent multiple notifications */
batch(fn: () => void): void;
}Usage Examples:
// Batch multiple field changes
const populateUserForm = (userData) => {
form.batch(() => {
form.change('firstName', userData.firstName);
form.change('lastName', userData.lastName);
form.change('email', userData.email);
form.change('phone', userData.phone);
form.change('address', userData.address);
form.change('city', userData.city);
form.change('state', userData.state);
form.change('zipCode', userData.zipCode);
}); // Only one notification sent after all changes
};
// Batch form operations
const resetAndPopulate = (newData) => {
form.batch(() => {
form.reset(); // Reset form
Object.keys(newData).forEach(key => {
form.change(key, newData[key]); // Set new values
});
});
};
// Batch with focus management
const setupFormForEditing = (data) => {
form.batch(() => {
form.initialize(data);
form.focus('firstName'); // Focus first field
});
};
// Complex batch operation with conditional logic
const handleBulkUpdate = (updates) => {
form.batch(() => {
updates.forEach(({ field, value, condition }) => {
if (!condition || condition(form.getState().values)) {
form.change(field, value);
}
});
// Focus first field that has an error after updates
const state = form.getState();
if (state.errors) {
const firstErrorField = Object.keys(state.errors)[0];
if (firstErrorField) {
form.focus(firstErrorField);
}
}
});
};