Formit API Reference
Complete API documentation for @vielzeug/formit.
Table of Contents
createForm()
Creates a new form instance with the specified configuration.
Signature
function createForm<TForm extends Record<string, any>>(init?: FormInit<TForm>): FormInstance<TForm>;Parameters
init?: FormInit<TForm>- Form configuration object
type FormInit<TForm> = {
initialValues?: TForm;
fields?: Partial<{
[K in keyof TForm & string]: FieldConfig<TForm[K], TForm>;
}>;
validate?: FormValidator<TForm>;
};Returns
A form instance with all the methods described below.
Example
import { createForm } from '@vielzeug/formit';
const form = createForm({
initialValues: {
name: '',
email: '',
age: 0,
},
fields: {
email: {
validators: (value) => {
if (!value.includes('@')) return 'Invalid email';
},
},
},
validate: (values) => {
if (values.age < 18) {
return { age: 'Must be at least 18 years old' };
}
},
});Form Methods
Value Management
getValues()
Get all form values.
Signature:
getValues(): TFormReturns: Complete form values object
Example:
const values = form.getValues();
console.log(values); // { name: 'John', email: 'john@example.com', age: 25 }getValue(path)
Get a specific field value by path.
Signature:
getValue(path: Path): anyParameters:
path: Path- Field path (string with dot/bracket notation or array)
Returns: The value at the specified path
Example:
const email = form.getValue('email');
const city = form.getValue('user.address.city');
const firstItem = form.getValue('items[0]');
const deepValue = form.getValue(['user', 'address', 'city']);setValue(path, value, options?)
Set a specific field value by path.
Signature:
setValue(
path: Path,
value: any,
options?: {
markDirty?: boolean;
markTouched?: boolean;
}
): anyParameters:
path: Path- Field pathvalue: any- New value to setoptions?: object- Optional configurationmarkDirty?: boolean- Mark field as dirty (default:true)markTouched?: boolean- Mark field as touched (default:false)
Returns: The value that was set
Example:
// Simple set
form.setValue('email', 'john@example.com');
// Set without marking as dirty
form.setValue('email', 'john@example.com', { markDirty: false });
// Set and mark as touched
form.setValue('email', 'john@example.com', { markTouched: true });
// Nested paths
form.setValue('user.address.city', 'New York');
form.setValue(['items', 0, 'name'], 'First Item');setValues(nextValues, options?)
Set multiple form values at once.
Signature:
setValues(
nextValues: Partial<TForm>,
options?: {
replace?: boolean;
markAllDirty?: boolean;
}
): voidParameters:
nextValues: Partial<TForm>- Object with values to updateoptions?: object- Optional configurationreplace?: boolean- Replace all values (default:false- merge)markAllDirty?: boolean- Mark all updated fields as dirty (default:false)
Example:
// Merge values
form.setValues({ name: 'John', email: 'john@example.com' });
// Replace all values
form.setValues({ name: 'Jane' }, { replace: true });
// Mark all as dirty
form.setValues({ email: 'new@example.com' }, { markAllDirty: true });Error Management
getErrors()
Get all form errors.
Signature:
getErrors(): ErrorsReturns: Object mapping field paths to error messages
Example:
const errors = form.getErrors();
console.log(errors); // { email: 'Invalid email', age: 'Must be 18+' }getError(path)
Get a specific field error by path.
Signature:
getError(path: Path): string | undefinedParameters:
path: Path- Field path
Returns: Error message string or undefined if no error
Example:
const emailError = form.getError('email');
if (emailError) {
console.log(emailError); // 'Invalid email'
}setError(path, message?)
Set or clear a specific field error.
Signature:
setError(path: Path, message?: string): voidParameters:
path: Path- Field pathmessage?: string- Error message (omit to clear error)
Example:
// Set error
form.setError('email', 'This email is already taken');
// Clear error
form.setError('email');resetErrors()
Clear all form errors.
Signature:
resetErrors(): voidExample:
form.resetErrors();
console.log(form.getErrors()); // {}Touch Management
markTouched(path)
Mark a field as touched.
Signature:
markTouched(path: Path): voidParameters:
path: Path- Field path
Example:
form.markTouched('email');
const state = form.getStateSnapshot();
console.log(state.touched.email); // trueValidation
validateField(path, signal?)
Validate a single field.
Signature:
validateField(path: Path, signal?: AbortSignal): Promise<string | undefined>Parameters:
path: Path- Field pathsignal?: AbortSignal- Optional abort signal for cancellation
Returns: Promise resolving to error message or undefined
Example:
const error = await form.validateField('email');
if (error) {
console.log('Email validation failed:', error);
}
// With abort signal
const controller = new AbortController();
form.validateField('email', controller.signal);
controller.abort(); // Cancel validationvalidateAll(signal?)
Validate all fields and run form-level validators.
Signature:
validateAll(signal?: AbortSignal): Promise<Errors>Parameters:
signal?: AbortSignal- Optional abort signal for cancellation
Returns: Promise resolving to all validation errors
Example:
const errors = await form.validateAll();
if (Object.keys(errors).length > 0) {
console.log('Form has errors:', errors);
}Form Submission
submit(onSubmit, options?)
Submit the form with automatic validation.
Signature:
submit(
onSubmit: (values: TForm) => MaybePromise<any>,
options?: {
signal?: AbortSignal;
validate?: boolean;
}
): Promise<any>Parameters:
onSubmit: (values: TForm) => MaybePromise<any>- Submit handler functionoptions?: object- Optional configurationsignal?: AbortSignal- Abort signal for cancellationvalidate?: boolean- Run validation before submit (default:true)
Returns: Promise resolving to the result of onSubmit
Rejects:
- With
{ errors, type: 'validation' }if validation fails - With the error thrown by
onSubmitif submission fails
Example:
// Basic submit
form.submit(async (values) => {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(values),
});
return response.json();
});
// Without validation
form.submit(
async (values) => {
await saveLocally(values);
},
{ validate: false },
);
// With abort signal
const controller = new AbortController();
form.submit(
async (values) => {
await api.post('/users', values);
},
{ signal: controller.signal },
);
// Error handling
try {
const result = await form.submit(async (values) => {
return await api.post('/users', values);
});
console.log('Success:', result);
} catch (error) {
if (error.type === 'validation') {
console.log('Validation errors:', error.errors);
} else {
console.error('Submission error:', error);
}
}Subscriptions
subscribe(listener)
Subscribe to form state changes.
Signature:
subscribe(listener: (state: FormState<TForm>) => void): () => voidParameters:
listener: (state: FormState<TForm>) => void- Callback function receiving form state
Returns: Unsubscribe function
Example:
const unsubscribe = form.subscribe((state) => {
console.log('Values:', state.values);
console.log('Errors:', state.errors);
console.log('Is submitting:', state.isSubmitting);
});
// Later, unsubscribe
unsubscribe();subscribeField(path, listener)
Subscribe to a specific field's changes.
Signature:
subscribeField(
path: Path,
listener: (payload: {
value: any;
error?: string;
touched: boolean;
dirty: boolean;
}) => void
): () => voidParameters:
path: Path- Field path to subscribe tolistener: Function- Callback receiving field state
Returns: Unsubscribe function
Example:
const unsubscribe = form.subscribeField('email', (field) => {
console.log('Email value:', field.value);
console.log('Email error:', field.error);
console.log('Email touched:', field.touched);
console.log('Email dirty:', field.dirty);
});
// Later
unsubscribe();Field Binding
bind(path)
Create a binding object for a field that can be used with inputs.
Signature:
bind(path: Path): {
name: string;
value: any;
onChange: (event: any) => void;
set: (value: any | ((prev: any) => any)) => void;
}Parameters:
path: Path- Field path
Returns: Binding object with:
name: string- Field name (dot notation)value: any- Current field value (getter)value: any- Value setter (setter)onChange: (event) => void- Change handler for inputsset: (value) => void- Value setter function (supports updater functions)
Example:
// Spread into input
<input {...form.bind('email')} />
// Equivalent to:
<input
name="email"
value={form.getValue('email')}
onChange={(e) => form.setValue('email', e.target.value, {
markDirty: true,
markTouched: true
})}
/>
// Use set function directly
const emailBinding = form.bind('email');
emailBinding.set('new@example.com');
// With updater function
emailBinding.set((prev) => prev.toLowerCase());State Snapshot
getStateSnapshot()
Get a snapshot of the current form state.
Signature:
getStateSnapshot(): FormState<TForm>Returns: Complete form state object
Example:
const state = form.getStateSnapshot();
console.log(state);
/*
{
values: { name: 'John', email: 'john@example.com' },
errors: { email: 'Invalid email' },
touched: { email: true },
dirty: { name: true },
isValidating: false,
isSubmitting: false,
submitCount: 2
}
*/Types
Path
Field path type supporting multiple formats.
type Path = string | Array<string | number>;Examples:
'email'; // Simple field
'user.name'; // Dot notation
'user.address.city'; // Deep nesting
'items[0]'; // Bracket notation
'items[0].name'[('user', 'name')][('items', 0, 'name')]; // Mixed notation // Array notation // Array with indicesErrors
Error object mapping field paths to error messages.
type Errors = Partial<Record<string, string>>;Example:
const errors: Errors = {
email: 'Invalid email',
'user.age': 'Must be at least 18',
'items[0].name': 'Name is required',
};FieldValidator<TValue, TForm>
Validator function for a specific field.
type FieldValidator<TValue, TForm> =
| ((value: TValue, values: TForm) => MaybePromise<string | undefined | null>)
| ((value: TValue, values: TForm) => MaybePromise<Record<string, string> | undefined | null>);Returns:
string- Error messageRecord<string, string>- Multiple errors for related fieldsundefined | null- No error
Example:
// Simple string error
const emailValidator: FieldValidator<string, FormData> = (value) => {
if (!value.includes('@')) return 'Invalid email';
};
// Multiple errors
const passwordValidator: FieldValidator<string, FormData> = (value) => {
if (value.length < 8) {
return { password: 'Too short', 'password-confirm': 'Must match' };
}
};
// Async validator
const usernameValidator: FieldValidator<string, FormData> = async (value) => {
const exists = await checkUsername(value);
if (exists) return 'Username taken';
};
// With access to all form values
const confirmPasswordValidator: FieldValidator<string, FormData> = (value, values) => {
if (value !== values.password) return 'Passwords do not match';
};FormValidator<TForm>
Form-level validator function.
type FormValidator<TForm> = (values: TForm) => MaybePromise<Errors | undefined | null>;Example:
const formValidator: FormValidator<FormData> = (values) => {
const errors: Errors = {};
if (values.password === values.email) {
errors.password = 'Password cannot be same as email';
}
if (values.age < 18 && values.acceptTerms) {
errors.acceptTerms = 'Minors cannot accept terms';
}
return errors;
};FieldConfig<TValue, TForm>
Configuration for a specific field.
type FieldConfig<TValue, TForm> = {
initialValue?: TValue;
validators?: FieldValidator<TValue, TForm> | Array<FieldValidator<TValue, TForm>>;
};Example:
const emailConfig: FieldConfig<string, FormData> = {
initialValue: '',
validators: [
(value) => {
if (!value) return 'Email is required';
},
(value) => {
if (!value.includes('@')) return 'Invalid email';
},
],
};FormInit<TForm>
Initial configuration for creating a form.
type FormInit<TForm> = {
initialValues?: TForm;
fields?: Partial<{
[K in keyof TForm & string]: FieldConfig<TForm[K], TForm>;
}>;
validate?: FormValidator<TForm>;
};FormState<TForm>
Complete form state object.
type FormState<TForm> = {
values: TForm;
errors: Errors;
touched: Record<string, boolean>;
dirty: Record<string, boolean>;
isValidating: boolean;
isSubmitting: boolean;
submitCount: number;
};Example:
const state: FormState<FormData> = {
values: { name: 'John', email: 'john@example.com' },
errors: { email: 'Already exists' },
touched: { email: true, name: false },
dirty: { name: true, email: true },
isValidating: false,
isSubmitting: true,
submitCount: 1,
};Advanced Patterns
Conditional Validation
const form = createForm({
fields: {
country: {},
zipCode: {
validators: (value, values) => {
if (values.country === 'US' && !/^\d{5}$/.test(value)) {
return 'Invalid US ZIP code';
}
if (values.country === 'UK' && !/^[A-Z]{1,2}\d{1,2}/.test(value)) {
return 'Invalid UK postcode';
}
},
},
},
});Debounced Validation
import { debounce } from '@vielzeug/toolkit';
const checkUsername = debounce(async (username: string) => {
const response = await fetch(`/api/check-username?username=${username}`);
return response.json();
}, 500);
const form = createForm({
fields: {
username: {
validators: async (value) => {
const { exists } = await checkUsername(value);
if (exists) return 'Username is taken';
},
},
},
});Dynamic Field Addition
// Start with initial fields
const form = createForm({
initialValues: { name: '' },
});
// Dynamically add fields
form.setValue('email', '');
form.setValue('phone', '');
// Validate dynamically added fields
form.validateField('email');Cross-Field Validation
const form = createForm({
validate: (values) => {
const errors: Errors = {};
// Password confirmation
if (values.password !== values.confirmPassword) {
errors.confirmPassword = 'Passwords must match';
}
// Date range
if (new Date(values.startDate) > new Date(values.endDate)) {
errors.endDate = 'End date must be after start date';
}
return errors;
},
});Wizard Forms
const form = createForm({
initialValues: {
step1: { name: '', email: '' },
step2: { address: '', city: '' },
step3: { payment: '' },
},
});
async function validateStep(step: number) {
const stepFields =
{
1: ['step1.name', 'step1.email'],
2: ['step2.address', 'step2.city'],
3: ['step3.payment'],
}[step] || [];
for (const field of stepFields) {
const error = await form.validateField(field);
if (error) return false;
}
return true;
}See Also
- Usage Guide - Common patterns and best practices
- Examples - Real-world examples