@s-chat/form-manager
v2.1.1
Published
[](https://gitlab.com/shaggrath-projects-1/public-projects/form-manager/-/commits/main) [ {
const {
fields,
formFieldObject,
validateForm,
resetForm,
bulkUpdateForm,
} = useFormManager<LoginFormValues>(loginFormSchema);
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
const validData = validateForm();
if (!validData) {
return;
}
console.log('Valid form data:', validData);
};
return (
<form onSubmit={handleSubmit}>
{fields.map((field) => (
<label key={String(field.key)} htmlFor={String(field.key)}>
{field.label}
<input {...field.inputProps} />
{field.isError && (
<span>{field.errorMessage}</span>
)}
</label>
))}
<pre>{JSON.stringify(formFieldObject, null, 2)}</pre>
<button type="submit">Submit</button>
<button type="button" onClick={resetForm}>Reset</button>
<button
type="button"
onClick={() => bulkUpdateForm({ email: '[email protected]' })}
>
Fill demo email
</button>
</form>
);
}
export function App() {
return (
<FormManagerProvider>
<LoginForm />
</FormManagerProvider>
);
}Field Schema
Each form is described by FormSchema<ValuesType>. The schema keys must match
the keys of your form values type.
type FormSchema<ValuesType> = {
[key in keyof ValuesType]: FieldSchema<ValuesType[key], ValuesType>;
};A field can contain:
| Property | Type | Description |
| --- | --- | --- |
| label | string | Human-readable field label. Returned as field.label. |
| type | string | Input type, for example text, email, password, checkbox, select. |
| validationRules | LIVRRuleDefinition | LIVR rules used by validateForm(). |
| defaultValue | ValueType | Initial value and the value restored by resetForm(). |
| options | FieldOptionType[] | Options for select-like controls. |
| showIf | Partial<ValuesType> | Conditions that control whether the field keeps its original type or becomes hidden. |
| htmlAttr | Record<string, any> | Extra props merged into field.inputProps. |
| errorMessages | Record<string, string> | Field-level error message metadata. |
| onBeforeChange | (value) => void | Called before the field state is updated. |
| onAfterChange | (value) => void | Called after the field state update is dispatched. |
Conditional Fields
Use showIf to show a field only when other form values match specific values.
The keys in showIf are form field keys, and the values are compared with the
current form values.
type ProfileFormValues = {
accountType: 'personal' | 'company';
companyName: string;
};
const profileFormSchema: FormSchema<ProfileFormValues> = {
accountType: {
label: 'Account type',
type: 'select',
defaultValue: 'personal',
validationRules: ['required'],
options: [
{ label: 'Personal', value: 'personal' },
{ label: 'Company', value: 'company' },
],
},
companyName: {
label: 'Company name',
type: 'text',
defaultValue: '',
validationRules: ['required'],
showIf: {
accountType: 'company',
},
},
};When the form is initialized, showIf is calculated from field
defaultValues. If the condition is not satisfied, the field receives
inputProps.type = 'hidden'.
When a form value changes through onChange() or bulkUpdateForm(), all fields
with showIf are recalculated:
| Condition | Result |
| --- | --- |
| All showIf values match current form values | The field receives its original type. |
| At least one showIf value does not match | The field receives type: 'hidden'. |
The field value is not cleared when the field becomes hidden.
Rendering Fields
useFormManager() returns a normalized fields array. Each item contains:
| Property | Description |
| --- | --- |
| key | Field key from the schema. |
| label | Field label from the schema. |
| inputProps | Props for your input component: value, type, name, id, options, onChange, plus htmlAttr. |
| isError | true when the field has a validation error. |
| errorMessage | Validation message mapped through FormManagerProvider.errorMessages, or the original LIVR error code when no mapping exists. |
| isRequired | true when validationRules includes required. |
Example with a custom renderer:
{fields.map((field) => {
if (field.inputProps.type === 'select') {
return (
<select
key={String(field.key)}
value={field.inputProps.value}
name={String(field.inputProps.name)}
id={String(field.inputProps.id)}
onChange={field.inputProps.onChange}
>
{field.inputProps.options?.map((option) => (
<option key={String(option.value)} value={String(option.value)}>
{option.label}
</option>
))}
</select>
);
}
return (
<input key={String(field.key)} {...field.inputProps} />
);
})}Validation
Validation is performed by validateForm().
const validData = validateForm();
if (validData) {
// validData has the same type as your form values type
}If the form is valid, validateForm() returns validated data. If the form is
invalid, it updates field errors and returns void.
Validation errors are resolved through FormManagerProvider.errorMessages.
LIVR returns error codes such as REQUIRED or WRONG_EMAIL; the hook uses
those codes as keys and stores the mapped message in field.errorMessage. If a
code is not present in errorMessages, the original code is used as the field
message.
The hook registers livr-extra-rules automatically, so the additional rules
from that package can be used in validationRules.
Form State Helpers
useFormManager() returns:
| Helper | Description |
| --- | --- |
| formFieldObject | Current form values object. |
| validateForm() | Validates the form and returns typed valid data or void. |
| resetForm() | Restores all fields to their defaultValue. |
| bulkUpdateForm(updateData) | Updates multiple field values at once. |
Example:
bulkUpdateForm({
email: '[email protected]',
rememberMe: true,
});Provider Configuration
FormManagerProvider can be used to customize value extraction and register
custom LIVR rules.
import { ChangeEvent } from 'react';
import { FormManagerProvider } from '@s-chat/form-manager';
export function App() {
return (
<FormManagerProvider
errorMessages={{
REQUIRED: 'This field is required',
WRONG_EMAIL: 'Enter a valid email address',
NOT_EVEN_NUMBER: 'Enter an even number',
}}
fieldValueSelector={{
checkbox: (event: ChangeEvent<HTMLInputElement>) => event.target.checked,
number: (event: ChangeEvent<HTMLInputElement>) => Number(event.target.value),
default: (event: ChangeEvent<HTMLInputElement>) => event.target.value,
}}
customValidationRules={{
even_number: () => (value: number) => (
value % 2 === 0 ? undefined : 'NOT_EVEN_NUMBER'
),
}}
options={{
isValidateOnChange: false,
}}
>
<LoginForm />
</FormManagerProvider>
);
}errorMessages
errorMessages maps LIVR error codes to user-facing validation messages.
Provider messages are merged with the default messages.
<FormManagerProvider
errorMessages={{
REQUIRED: 'Required field',
CANNOT_BE_EMPTY: 'Required field',
WRONG_EMAIL: 'Invalid email',
}}
>
<Form />
</FormManagerProvider>Default messages include:
| Code | Default message |
| --- | --- |
| DEFAULT | Wrong format |
| REQUIRED | Current field is required! |
| CANNOT_BE_EMPTY | Current field is required! |
| NOT_DECIMAL | Wrong data type! |
| NOT_POSITIVE_DECIMAL | Wrong data type! |
| NOT_INTEGER | Wrong data type! |
| NOT_POSITIVE_INTEGER | Wrong data type! |
| WRONG_EMAIL | Is invalid email! |
| FIELDS_NOT_EQUAL | Not equal! |
| TOO_LOW | Not valid value! |
| TOO_HIGH | Not valid value! |
| DATE_UNDER_MAX_RANGE | Date is under max range! |
| DATE_BELOW_MIN_RANGE | Date is below min range! |
Custom validation rules can return custom error codes. Add matching keys to
errorMessages to display custom text for those errors.
fieldValueSelector
fieldValueSelector defines how onChange arguments are converted into field
values.
Default selectors:
| Type | Selector |
| --- | --- |
| checkbox | event.target.checked |
| default | event.target.value |
The hook chooses the selector by the field's initial type. If no selector for
that type exists, it uses default.
options.isValidateOnChange
Pass options.isValidateOnChange = true to validate after field changes.
const form = useFormManager(formSchema, {
isValidateOnChange: true,
});The provider can also define the default value for this option:
<FormManagerProvider options={{ isValidateOnChange: true }}>
<Form />
</FormManagerProvider>Type Reference
FieldOptionType<ValueType = string>
type FieldOptionType<ValueType = string> = {
label: ReactNode;
value: ValueType;
};Used for select, radio, checkbox group, or any custom option-based input.
ErrorListType
type ErrorListType = Record<string, string>;Map of validation error codes to messages.
FormManagerInputProps
type FormManagerInputProps<ValuesType, KeyType extends keyof ValuesType> = {
value: ValuesType[KeyType];
type: string;
name: KeyType;
id: KeyType;
options?: FieldOptionType[];
onChange: (...attrs: any[]) => void;
} & Record<string, any>;Props generated for an input component. htmlAttr from the field schema is
merged into this object.
FormManagerField
type FormManagerField<ValuesType, KeyType extends keyof ValuesType> = {
inputProps: FormManagerInputProps<ValuesType, KeyType>;
key: KeyType;
label: string;
isError: boolean;
errorMessage: string;
isRequired: boolean;
};Normalized field data returned in the fields array.
FieldSchema
type FieldSchema<ValueType = string, ValuesType = UniversalValuesType> = {
label: string;
type: string;
validationRules: LIVRRuleDefinition;
defaultValue?: ValueType;
options?: FieldOptionType[];
showIf?: Partial<{ [key in keyof ValuesType]: ValuesType[key] }>;
htmlAttr?: Record<string, any>;
errorMessages?: ErrorListType;
onBeforeChange?: (value: ValueType) => void;
onAfterChange?: (value: ValueType) => void;
};showIf is typed against the full form values type, so a field can depend on
the value of any other field in the same FormSchema.
UseFormManagerReturnType
type UseFormManagerReturnType<ValuesType> = {
fields: FormManagerField<ValuesType>[];
formFieldObject: ValuesType;
validateForm: () => ValuesType | void;
resetForm: () => void;
bulkUpdateForm: (updateData: Partial<ValuesType>) => void;
};Return type of useFormManager().
Build
npm run buildThe build script creates CommonJS output in dist and ESM output in
dist/ejs.
Tests
npm run test:runThe test suite uses Vitest with jsdom and React Testing Library. It covers
conditional fields through showIf, validation message mapping through
FormManagerProvider.errorMessages, bulkUpdateForm(), resetForm(), and
isValidateOnChange.
For watch mode:
npm testTo generate a coverage report:
npm run test:coverageThe HTML report is written to coverage/.
