@ahrowe/ui 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +353 -348
- package/dist/index.mjs.map +1 -1
- package/dist/types/package/common/virtualList/useColumns.d.ts +3 -1
- package/dist/types/package/common/virtualList/useVirtualWindow.d.ts +1 -0
- package/docs/CLAUDE.md +19 -26
- package/docs/FormValidator.md +146 -0
- package/docs/FormValidatorGroup.md +154 -0
- package/package.json +1 -1
|
@@ -13,6 +13,8 @@ interface UseColumnsArgs<T> {
|
|
|
13
13
|
startIndex: number;
|
|
14
14
|
endIndex: number;
|
|
15
15
|
containerHeight: number;
|
|
16
|
+
/** Re-measure `fit` columns when the container width settles (e.g. after a route transition). */
|
|
17
|
+
containerWidth: number;
|
|
16
18
|
}
|
|
17
19
|
interface UseColumnsResult<T> {
|
|
18
20
|
hasColumns: boolean;
|
|
@@ -33,5 +35,5 @@ interface UseColumnsResult<T> {
|
|
|
33
35
|
* `fit`-column width measurement that pins one shared px track across the header
|
|
34
36
|
* and every row grid so they stay aligned.
|
|
35
37
|
*/
|
|
36
|
-
export declare function useColumns<T>({ columns, items, multiSelect, visibleColumnKeys, onVisibleColumnsChange, persistColumnsKey, showColumnToggle, rootRef, startIndex, endIndex, containerHeight, }: UseColumnsArgs<T>): UseColumnsResult<T>;
|
|
38
|
+
export declare function useColumns<T>({ columns, items, multiSelect, visibleColumnKeys, onVisibleColumnsChange, persistColumnsKey, showColumnToggle, rootRef, startIndex, endIndex, containerHeight, containerWidth, }: UseColumnsArgs<T>): UseColumnsResult<T>;
|
|
37
39
|
export {};
|
package/docs/CLAUDE.md
CHANGED
|
@@ -37,7 +37,7 @@ const myTheme: Theme = {
|
|
|
37
37
|
|
|
38
38
|
## FormValidator (for form-connected components)
|
|
39
39
|
|
|
40
|
-
Input, NumberInput, Dropdown, Checkbox all accept a `formValidator` prop. Use it to connect validation state.
|
|
40
|
+
Input, NumberInput, Dropdown, Checkbox, DatePicker, IconPicker, InputDropdown, and Textarea all accept a `formValidator` prop. Use it to connect validation state.
|
|
41
41
|
|
|
42
42
|
```tsx
|
|
43
43
|
import { FormValidator, Validators, Input } from '@ahrowe/ui';
|
|
@@ -47,38 +47,29 @@ const nameValidator = new FormValidator('', [Validators.required(), Validators.m
|
|
|
47
47
|
|
|
48
48
|
// Pass to component — it tracks value, dirty, touched, errors internally
|
|
49
49
|
<Input label="Name" formValidator={nameValidator} />
|
|
50
|
-
|
|
51
|
-
// Read state
|
|
52
|
-
nameValidator.value // current value
|
|
53
|
-
nameValidator.hasError() // true if any error
|
|
54
|
-
nameValidator.getCurrentErrorMessage() // first error message or null
|
|
55
|
-
nameValidator.touched // user has blurred the field
|
|
56
|
-
nameValidator.dirty // value has changed
|
|
57
|
-
|
|
58
|
-
// Validators available
|
|
59
|
-
Validators.required()
|
|
60
|
-
Validators.minLength(n)
|
|
61
|
-
Validators.maxLength(n)
|
|
62
|
-
Validators.email()
|
|
63
|
-
Validators.mustBeNumber()
|
|
64
|
-
Validators.date()
|
|
65
|
-
Validators.website()
|
|
66
|
-
Validators.password() // min 8 chars, 1 letter, 1 digit
|
|
67
|
-
Validators.oneOf([...])
|
|
68
|
-
Validators.multiEmail()
|
|
69
|
-
Validators.bic()
|
|
70
50
|
```
|
|
71
51
|
|
|
72
|
-
For
|
|
52
|
+
For multi-field forms with a single submit handler, use `useFormValidatorGroup`:
|
|
73
53
|
|
|
74
54
|
```tsx
|
|
75
|
-
import {
|
|
55
|
+
import { useFormValidatorGroup, FormValidator, Validators } from '@ahrowe/ui';
|
|
56
|
+
|
|
57
|
+
const form = useFormValidatorGroup(
|
|
58
|
+
{
|
|
59
|
+
name: new FormValidator('', [Validators.required()]),
|
|
60
|
+
email: new FormValidator('', [Validators.required(), Validators.email()]),
|
|
61
|
+
},
|
|
62
|
+
{ onSubmit: async (values) => save(values) }, // values is fully typed
|
|
63
|
+
);
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
group.
|
|
79
|
-
|
|
65
|
+
<form.Form>
|
|
66
|
+
<Input label="Name" formValidator={form.group.name} />
|
|
67
|
+
<Input label="Email" formValidator={form.group.email} />
|
|
68
|
+
</form.Form>
|
|
80
69
|
```
|
|
81
70
|
|
|
71
|
+
Full reference: see **FormValidator.md** (single field, all built-in validators, custom validators) and **FormValidatorGroup.md** (whole-form submit, validate, reset, server-error mapping) below.
|
|
72
|
+
|
|
82
73
|
## Slot customisation (classNames / styles)
|
|
83
74
|
|
|
84
75
|
Most components expose `classNames` and `styles` props for styling inner elements without fighting CSS specificity.
|
|
@@ -116,6 +107,8 @@ Slot keys per component are documented in each component's doc file below.
|
|
|
116
107
|
@EmptyState.md
|
|
117
108
|
@Fab.md
|
|
118
109
|
@FloatingMenu.md
|
|
110
|
+
@FormValidator.md
|
|
111
|
+
@FormValidatorGroup.md
|
|
119
112
|
@IconPicker.md
|
|
120
113
|
@IdleManager.md
|
|
121
114
|
@InfiniteBlock.md
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# FormValidator
|
|
2
|
+
|
|
3
|
+
**When to use:** Track and validate a single form field's value, dirty/touched state, and errors. Pass the instance to any form-connected component (`Input`, `NumberInput`, `Dropdown`, `Checkbox`, `DatePicker`, `IconPicker`, `InputDropdown`, `Textarea`) via its `formValidator` prop — the component then reads the value and surfaces validation errors automatically. For multi-field forms with submit handling, use `FormValidatorGroup` instead.
|
|
4
|
+
|
|
5
|
+
**Import:** `import { FormValidator, Validators, useFormValidator } from '@ahrowe/ui'`
|
|
6
|
+
**Types:** `import type { ValidatorFunction, ValidationError } from '@ahrowe/ui'`
|
|
7
|
+
|
|
8
|
+
## Basic usage
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { FormValidator, Validators, Input } from '@ahrowe/ui';
|
|
12
|
+
|
|
13
|
+
// Create with a default (empty) value and an optional list of validators
|
|
14
|
+
const nameValidator = new FormValidator('', [Validators.required(), Validators.minLength(2)]);
|
|
15
|
+
|
|
16
|
+
// Hand it to a form component — it tracks value, dirty, touched, and errors internally
|
|
17
|
+
<Input label="Name" formValidator={nameValidator} />
|
|
18
|
+
|
|
19
|
+
// Read state at any time
|
|
20
|
+
nameValidator.value // current value (typed as T)
|
|
21
|
+
nameValidator.hasError() // true if any error is present
|
|
22
|
+
nameValidator.hasError('required') // true if the error with id 'required' is present
|
|
23
|
+
nameValidator.getCurrentErrorMessage() // first error message, or null
|
|
24
|
+
nameValidator.touched // user has entered and left the field
|
|
25
|
+
nameValidator.dirty // value has changed since creation
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> **The first constructor argument is the field's _default (empty)_ value** — what the field holds when nothing has been entered, **not** an existing record's value. Use the type's natural empty value:
|
|
29
|
+
>
|
|
30
|
+
> | Field | Default value |
|
|
31
|
+
> |-------|---------------|
|
|
32
|
+
> | string (text, email, …) | `''` |
|
|
33
|
+
> | `Date` | `null` |
|
|
34
|
+
> | number | `undefined` (or `0`) |
|
|
35
|
+
> | checkbox / boolean | `false` |
|
|
36
|
+
> | single-select id | `null` |
|
|
37
|
+
>
|
|
38
|
+
> To pre-fill fields when **editing an existing entity**, don't seed values here — pass them as `initialValues` on a `FormValidatorGroup` (see [FormValidatorGroup.md](FormValidatorGroup.md)). That keeps the empty value as the validator's true default so `reset()` works correctly.
|
|
39
|
+
|
|
40
|
+
## Re-rendering a function component when the value changes
|
|
41
|
+
|
|
42
|
+
A `FormValidator` lives outside React state, so changing it does **not** by itself re-render a function component. When you read `validator.value` / `validator.hasError()` directly in render (rather than only handing the instance to a form component), subscribe with `useFormValidator` so the component re-renders on every change:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { useMemo } from 'react';
|
|
46
|
+
import { FormValidator, Validators, useFormValidator, Input } from '@ahrowe/ui';
|
|
47
|
+
|
|
48
|
+
function NameField() {
|
|
49
|
+
// Create once — never `new FormValidator(...)` inline in render without memoising
|
|
50
|
+
const validator = useMemo(() => new FormValidator('', [Validators.required()]), []);
|
|
51
|
+
useFormValidator(validator); // re-renders this component on value/error/touched/dirty changes
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
<Input label="Name" formValidator={validator} />
|
|
56
|
+
<button disabled={validator.hasError()}>Save</button>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`useFormValidator(validator)` returns the same instance for convenience and accepts `null`.
|
|
63
|
+
|
|
64
|
+
## Typing the value
|
|
65
|
+
|
|
66
|
+
`FormValidator<T>` is generic. Pass the value type for non-string fields, and give each its natural empty value as the default:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
const ageValidator = new FormValidator<number | undefined>(undefined, [Validators.required()]);
|
|
70
|
+
const dateValidator = new FormValidator<Date | null>(null);
|
|
71
|
+
const agreedValidator = new FormValidator<boolean>(false, [Validators.required()]);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Instance API
|
|
75
|
+
|
|
76
|
+
| Member | Signature | Description |
|
|
77
|
+
|--------|-----------|-------------|
|
|
78
|
+
| `value` | `T` (get/set) | Current value. The setter assigns **without** validating — prefer `set()` for user input. |
|
|
79
|
+
| `set` | `(newValue: T, wasUser = true) => this` | Assign value, mark dirty when `wasUser`, then re-validate. Chainable. |
|
|
80
|
+
| `validate` | `() => void` | Clear errors and run every validator against the current value. |
|
|
81
|
+
| `touched` | `boolean` (get/set) | User has entered and left the field. |
|
|
82
|
+
| `dirty` | `boolean` (get/set) | Value changed since creation. |
|
|
83
|
+
| `errors` | `ValidationError[]` (get) | All current errors (`{ id, message }`). |
|
|
84
|
+
| `hasError` | `(id?: string) => boolean` | Any error when called with no arg; a specific error by `id` otherwise. |
|
|
85
|
+
| `getCurrentErrorMessage` | `() => string \| null` | First error message, or `null`. |
|
|
86
|
+
| `addError` | `(error: ValidationError) => void` | Push an error manually (e.g. a server-side error). |
|
|
87
|
+
| `removeErrors` | `(ids: string[]) => void` | Remove errors by id. |
|
|
88
|
+
| `clearErrors` | `() => void` | Remove all errors. |
|
|
89
|
+
| `label` | `string` (get/set) | Field label; form components set this and validators receive it (used by `required()` messages). |
|
|
90
|
+
| `validators` | `ValidatorFunction[]` | The active validator list — mutable. |
|
|
91
|
+
| `registerOnUpdateListener` | `(cb: () => void) => void` | Subscribe to changes (what `useFormValidator` uses internally). |
|
|
92
|
+
| `removeOnUpdateListener` | `(cb: () => void) => void` | Unsubscribe. |
|
|
93
|
+
|
|
94
|
+
> Validators run once in the constructor, so `hasError()` reflects the initial value immediately.
|
|
95
|
+
|
|
96
|
+
## Built-in validators
|
|
97
|
+
|
|
98
|
+
All live as static methods on `Validators`. Each returns a `ValidatorFunction` and accepts an optional custom `errorMsg` as the last argument to override the default message.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
Validators.required(errorMsg?) // value must be present (0 counts as present)
|
|
102
|
+
Validators.minLength(min, errorMsg?) // string length >= min (also fails on empty)
|
|
103
|
+
Validators.maxLength(max, errorMsg?) // string length <= max
|
|
104
|
+
Validators.email(errorMsg?) // valid email address
|
|
105
|
+
Validators.multiEmail(errorMsg?) // comma/semicolon-separated list of emails
|
|
106
|
+
Validators.mustBeNumber(errorMsg?) // parses as a number
|
|
107
|
+
Validators.oneOf(allowedValues, errorMsg?) // value is in the allowed array
|
|
108
|
+
Validators.typeOf(allowedTypes, errorMsg?) // typeof value matches (string or string[])
|
|
109
|
+
Validators.date(errorMsg?) // valid date string (DD.MM.YYYY or ISO)
|
|
110
|
+
Validators.dateObject(errorMsg?) // an object with an `isValid` flag (e.g. a date wrapper)
|
|
111
|
+
Validators.website(errorMsg?) // valid URL
|
|
112
|
+
Validators.bic(errorMsg?) // valid BIC/SWIFT code
|
|
113
|
+
Validators.minLetters(min, errorMsg?) // at least `min` letters (a–z, incl. äöüß)
|
|
114
|
+
Validators.minDigits(min, errorMsg?) // at least `min` digits
|
|
115
|
+
Validators.password(errorMsg?) // min 8 chars, ≥1 letter, ≥1 digit
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> **Empty values pass most validators.** Except `required()` and `minLength()`, every validator returns `null` for an empty value — so a field is "valid when empty" unless you also add `Validators.required()`. Combine them:
|
|
119
|
+
> `new FormValidator('', [Validators.required(), Validators.email()])`.
|
|
120
|
+
|
|
121
|
+
## Custom validators
|
|
122
|
+
|
|
123
|
+
A validator is any `(value, label?) => ValidationError | null` — return `null` when valid, or `{ id, message }` when not:
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
import type { ValidatorFunction } from '@ahrowe/ui';
|
|
127
|
+
|
|
128
|
+
const mustBeEven: ValidatorFunction = (value) =>
|
|
129
|
+
Number(value) % 2 === 0 ? null : { id: 'even', message: 'Must be an even number' };
|
|
130
|
+
|
|
131
|
+
const validator = new FormValidator<number>(0, [Validators.required(), mustBeEven]);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The `id` you choose is what `hasError('even')` and `removeErrors(['even'])` match against.
|
|
135
|
+
|
|
136
|
+
## Reading server-side errors back into a field
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
validator.addError({ id: 'taken', message: 'This email is already registered' });
|
|
140
|
+
// later, once the user edits the field again:
|
|
141
|
+
validator.removeErrors(['taken']);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
When validating a whole form at once, prefer `FormValidatorGroup.parseError()` — see `FormValidatorGroup`.
|
|
145
|
+
</content>
|
|
146
|
+
</invoke>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# FormValidatorGroup
|
|
2
|
+
|
|
3
|
+
**When to use:** Manage a whole form — multiple fields, a single submit handler, validate-on-submit, dirty/touched tracking across the form, and mapping server-side errors back onto the right fields. Build it from a map of named `FormValidator` instances. For a single field, use `FormValidator` on its own.
|
|
4
|
+
|
|
5
|
+
**Import:** `import { useFormValidatorGroup, FormValidatorGroup, FormValidator, Validators } from '@ahrowe/ui'`
|
|
6
|
+
**Types:** `import type { FormValidatorGroupOptions, InferGroupValues, MergedValues } from '@ahrowe/ui'`
|
|
7
|
+
|
|
8
|
+
## Recommended: the `useFormValidatorGroup` hook
|
|
9
|
+
|
|
10
|
+
In function components, create the group with the hook. It instantiates the group once on mount, keeps `onSubmit`/`onValidate` fresh via refs, and re-renders the component whenever any field, the submit state, or validation state changes.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { useFormValidatorGroup, FormValidator, Validators, Input, InputType, ActionButtons } from '@ahrowe/ui';
|
|
14
|
+
|
|
15
|
+
function ProfileForm({ user, onSaved }) {
|
|
16
|
+
const form = useFormValidatorGroup(
|
|
17
|
+
{
|
|
18
|
+
// Each FormValidator's first arg is the DEFAULT (empty) value, not the user's value
|
|
19
|
+
name: new FormValidator('', [Validators.required(), Validators.minLength(2)]),
|
|
20
|
+
email: new FormValidator('', [Validators.required(), Validators.email()]),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
// initialValues OVERRIDES those defaults with the existing record you're editing
|
|
24
|
+
initialValues: { name: user.name, email: user.email },
|
|
25
|
+
onSubmit: async (values) => {
|
|
26
|
+
// `values` is fully typed: { name: string; email: string }
|
|
27
|
+
await api.updateProfile(values);
|
|
28
|
+
onSaved();
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// `form.Form` wires up onSubmit + noValidate for you
|
|
34
|
+
return (
|
|
35
|
+
<form.Form>
|
|
36
|
+
<Input label="Name" formValidator={form.group.name} />
|
|
37
|
+
<Input label="Email" type={InputType.Email} formValidator={form.group.email} />
|
|
38
|
+
<ActionButtons
|
|
39
|
+
submitLabel="Save"
|
|
40
|
+
onSubmit={form.submit}
|
|
41
|
+
isSubmitButtonLoading={form.isSubmitting}
|
|
42
|
+
isSubmitButtonDisabled={!form.isDirty}
|
|
43
|
+
/>
|
|
44
|
+
</form.Form>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> The exported `FormValidatorGroup` is the form-aware subclass: every instance has a `Form` component (`form.Form`) that renders `<form onSubmit={form.submit} noValidate>`. You can also call `form.submit(event)` directly from a button.
|
|
50
|
+
|
|
51
|
+
> **Defaults vs. `initialValues` — the editing pattern.** Each `FormValidator`'s first argument is the field's _empty default_ (`''`, `null`, `false`, …). When you load an existing entity to **edit** it (e.g. an existing user), put those current values in `initialValues` — not in the validator constructors. The group seeds each field from `initialValues` on creation, and that same set becomes the baseline `reset()` restores to. Keep the constructors as empty defaults so a "create new" form (no `initialValues`) and an "edit" form share the exact same field definitions.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// Create form — no initialValues, fields stay at their empty defaults
|
|
55
|
+
const createForm = useFormValidatorGroup(fields, { onSubmit: create });
|
|
56
|
+
|
|
57
|
+
// Edit form — same `fields`, current record passed as initialValues
|
|
58
|
+
const editForm = useFormValidatorGroup(fields, { initialValues: user, onSubmit: update });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Submit flow
|
|
62
|
+
|
|
63
|
+
`submit(event?)` does the following:
|
|
64
|
+
|
|
65
|
+
1. `preventDefault` / `stopPropagation` on the event if present.
|
|
66
|
+
2. Sets `isSubmitting` true.
|
|
67
|
+
3. On the **first** submit, runs `validate()` (validates every field + marks them touched); on later submits, runs `onValidate` only.
|
|
68
|
+
4. If any field error or a custom validation error exists → calls `onSubmitError` with it, increments `submitCount`, and stops (does **not** call `onSubmit`).
|
|
69
|
+
5. Otherwise calls `onSubmit(values, group)`. If it throws, the error is passed to `parseError(err, true)` to surface field-level or generic errors.
|
|
70
|
+
6. Sets `isSubmitting` false.
|
|
71
|
+
|
|
72
|
+
By default `onSubmitError` shows the first error as an error **toast** (requires a `ToastProvider` in the tree — see `Toast`). Override it via `options.onSubmitError`.
|
|
73
|
+
|
|
74
|
+
## Options
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
interface FormValidatorGroupOptions {
|
|
78
|
+
initialValues?: Record<string, unknown>; // OVERRIDES the validators' empty defaults with an
|
|
79
|
+
// existing record's values (use when editing); also
|
|
80
|
+
// becomes the baseline reset() restores to
|
|
81
|
+
onSubmit?: (values, group) => void | Promise<void>;
|
|
82
|
+
onValidate?: (group) => ValidationError | null | void; // extra cross-field validation
|
|
83
|
+
errorParser?: (err: unknown) => ValidationError[]; // turn an API error into field errors
|
|
84
|
+
onSubmitError?: (errors: ValidationError[]) => void; // default: error toast of the first message
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`onValidate` runs as part of `submit` — return a `{ id, message }` to block submission with a form-level error (e.g. "passwords must match"):
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
useFormValidatorGroup(
|
|
92
|
+
{ password: new FormValidator(''), confirm: new FormValidator('') },
|
|
93
|
+
{
|
|
94
|
+
onValidate: (group) =>
|
|
95
|
+
group.group.password.value === group.group.confirm.value
|
|
96
|
+
? null
|
|
97
|
+
: { id: 'mismatch', message: 'Passwords do not match' },
|
|
98
|
+
onSubmit: async (values) => api.setPassword(values),
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Instance API
|
|
104
|
+
|
|
105
|
+
| Member | Signature | Description |
|
|
106
|
+
|--------|-----------|-------------|
|
|
107
|
+
| `group` | `Record<name, FormValidator>` | The field validators. Access a field via `form.group.<name>`. |
|
|
108
|
+
| `Form` | `React.FC<FormHTMLAttributes>` | `<form>` pre-wired with `onSubmit={submit}` + `noValidate`. |
|
|
109
|
+
| `submit` | `(event?) => Promise<void>` | Validate then run `onSubmit`. Safe to pass straight to a `<form onSubmit>` or button. |
|
|
110
|
+
| `validate` | `() => ValidationError \| null \| void` | Validate every field, mark all touched, run `onValidate`. |
|
|
111
|
+
| `getValues` | `() => MergedValues` | Typed snapshot: `initialValues` merged with every field's current value. |
|
|
112
|
+
| `set` | `(name, value) => this` | Set one field's value (validates it). Chainable. |
|
|
113
|
+
| `setValidator` | `(name, validator) => this` | Swap a field's `FormValidator` instance. |
|
|
114
|
+
| `reset` | `(overrides?) => void` | Restore fields to their post-creation values (or `overrides`), clear dirty/touched, fire `onReset`. |
|
|
115
|
+
| `resetDirtyAndTouched` | `() => void` | Clear dirty/touched on all fields without changing values. |
|
|
116
|
+
| `parseError` | `(err, isInternal?) => void` | Run `errorParser` and push field errors onto matching validators; otherwise a generic error. |
|
|
117
|
+
| `getFormError` | `() => ValidationError \| null` | First error across all fields, or `null`. |
|
|
118
|
+
| `isSubmitting` | `boolean` (get) | True while `submit` is running. |
|
|
119
|
+
| `isDirty` | `boolean` (get) | Any field changed since creation. |
|
|
120
|
+
| `wasTouched` | `boolean` (get) | Any field entered and left. |
|
|
121
|
+
| `hasErrors` | `boolean` (get) | Any field currently has an error. |
|
|
122
|
+
| `initialValues` | `object` (get) | Copy of the initial values. |
|
|
123
|
+
| `submitCount` | `number` | How many times `submit` has run (reset to 0 on any field change). |
|
|
124
|
+
| `onDebounce` | `(values) => void` | Assignable callback fired 500ms after the last field change. |
|
|
125
|
+
| `onReset` | `(group, valuesAfterCreation, initialValues) => void` | Assignable, fired by `reset()`. |
|
|
126
|
+
|
|
127
|
+
## Mapping API errors back to fields
|
|
128
|
+
|
|
129
|
+
If your API returns errors shaped like `{ errors: [{ <fieldName>: '<message>' }] }`, the default `errorParser` routes each message to the matching field. A thrown `onSubmit` error is parsed automatically; you can also call it manually:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
try {
|
|
133
|
+
await api.save(form.getValues());
|
|
134
|
+
} catch (err) {
|
|
135
|
+
form.parseError(err); // pushes e.g. an "email already taken" error onto form.group.email
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Pass a custom `errorParser` in the options when your API uses a different shape.
|
|
140
|
+
|
|
141
|
+
## Class components
|
|
142
|
+
|
|
143
|
+
For legacy class components, instantiate directly and pass the component instance so the group can call `forceUpdate()`:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
this.form = new FormValidatorGroup(
|
|
147
|
+
{ name: new FormValidator('', [Validators.required()]) },
|
|
148
|
+
this, // component instance — enables re-render via forceUpdate
|
|
149
|
+
{ onSubmit: (values) => this.save(values) },
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`ValidatableComponent` (also exported) is the base class the built-in form components extend — subclass it when building your own validator-aware input.
|
|
154
|
+
</content>
|