mobx-formkit 1.0.0
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/AGENTS.md +761 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/lib/ArrayMap.d.ts +50 -0
- package/lib/ArrayMap.d.ts.map +1 -0
- package/lib/ArrayMap.js +151 -0
- package/lib/ArrayMap.js.map +1 -0
- package/lib/Base.d.ts +237 -0
- package/lib/Base.d.ts.map +1 -0
- package/lib/Base.js +872 -0
- package/lib/Base.js.map +1 -0
- package/lib/Bindings.d.ts +28 -0
- package/lib/Bindings.d.ts.map +1 -0
- package/lib/Bindings.js +73 -0
- package/lib/Bindings.js.map +1 -0
- package/lib/Field.d.ts +195 -0
- package/lib/Field.d.ts.map +1 -0
- package/lib/Field.js +734 -0
- package/lib/Field.js.map +1 -0
- package/lib/Form.d.ts +75 -0
- package/lib/Form.d.ts.map +1 -0
- package/lib/Form.js +199 -0
- package/lib/Form.js.map +1 -0
- package/lib/Options.d.ts +9 -0
- package/lib/Options.d.ts.map +1 -0
- package/lib/Options.js +85 -0
- package/lib/Options.js.map +1 -0
- package/lib/State.d.ts +43 -0
- package/lib/State.d.ts.map +1 -0
- package/lib/State.js +147 -0
- package/lib/State.js.map +1 -0
- package/lib/Validator.d.ts +25 -0
- package/lib/Validator.d.ts.map +1 -0
- package/lib/Validator.js +143 -0
- package/lib/Validator.js.map +1 -0
- package/lib/compat.d.ts +29 -0
- package/lib/compat.d.ts.map +1 -0
- package/lib/compat.js +117 -0
- package/lib/compat.js.map +1 -0
- package/lib/composer.d.ts +26 -0
- package/lib/composer.d.ts.map +1 -0
- package/lib/composer.js +65 -0
- package/lib/composer.js.map +1 -0
- package/lib/esm/ArrayMap.js +146 -0
- package/lib/esm/ArrayMap.js.map +1 -0
- package/lib/esm/Base.js +868 -0
- package/lib/esm/Base.js.map +1 -0
- package/lib/esm/Bindings.js +69 -0
- package/lib/esm/Bindings.js.map +1 -0
- package/lib/esm/Field.js +730 -0
- package/lib/esm/Field.js.map +1 -0
- package/lib/esm/Form.js +195 -0
- package/lib/esm/Form.js.map +1 -0
- package/lib/esm/Options.js +81 -0
- package/lib/esm/Options.js.map +1 -0
- package/lib/esm/State.js +143 -0
- package/lib/esm/State.js.map +1 -0
- package/lib/esm/Validator.js +139 -0
- package/lib/esm/Validator.js.map +1 -0
- package/lib/esm/compat.js +96 -0
- package/lib/esm/compat.js.map +1 -0
- package/lib/esm/composer.js +63 -0
- package/lib/esm/composer.js.map +1 -0
- package/lib/esm/index.js +8 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/models/FieldProps.js +116 -0
- package/lib/esm/models/FieldProps.js.map +1 -0
- package/lib/esm/models/OptionsModel.js +51 -0
- package/lib/esm/models/OptionsModel.js.map +1 -0
- package/lib/esm/models/StateInterface.js +9 -0
- package/lib/esm/models/StateInterface.js.map +1 -0
- package/lib/esm/models/ValidatorInterface.js +8 -0
- package/lib/esm/models/ValidatorInterface.js.map +1 -0
- package/lib/esm/parser.js +235 -0
- package/lib/esm/parser.js.map +1 -0
- package/lib/esm/props.js +118 -0
- package/lib/esm/props.js.map +1 -0
- package/lib/esm/utils.js +156 -0
- package/lib/esm/utils.js.map +1 -0
- package/lib/esm/validators/AJV.js +105 -0
- package/lib/esm/validators/AJV.js.map +1 -0
- package/lib/esm/validators/DVR.js +104 -0
- package/lib/esm/validators/DVR.js.map +1 -0
- package/lib/esm/validators/JOI.js +54 -0
- package/lib/esm/validators/JOI.js.map +1 -0
- package/lib/esm/validators/VALIBOT.js +50 -0
- package/lib/esm/validators/VALIBOT.js.map +1 -0
- package/lib/esm/validators/VINEJS.js +60 -0
- package/lib/esm/validators/VINEJS.js.map +1 -0
- package/lib/esm/validators/VJF.js +88 -0
- package/lib/esm/validators/VJF.js.map +1 -0
- package/lib/esm/validators/YUP.js +69 -0
- package/lib/esm/validators/YUP.js.map +1 -0
- package/lib/esm/validators/ZOD.js +43 -0
- package/lib/esm/validators/ZOD.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +15 -0
- package/lib/index.js.map +1 -0
- package/lib/models/BaseInterface.d.ts +40 -0
- package/lib/models/BaseInterface.d.ts.map +1 -0
- package/lib/models/BindingsInterface.d.ts +18 -0
- package/lib/models/BindingsInterface.d.ts.map +1 -0
- package/lib/models/FieldInterface.d.ts +88 -0
- package/lib/models/FieldInterface.d.ts.map +1 -0
- package/lib/models/FieldProps.d.ts +111 -0
- package/lib/models/FieldProps.d.ts.map +1 -0
- package/lib/models/FieldProps.js +116 -0
- package/lib/models/FieldProps.js.map +1 -0
- package/lib/models/FormInterface.d.ts +184 -0
- package/lib/models/FormInterface.d.ts.map +1 -0
- package/lib/models/OptionsInterface.d.ts +8 -0
- package/lib/models/OptionsInterface.d.ts.map +1 -0
- package/lib/models/OptionsModel.d.ts +99 -0
- package/lib/models/OptionsModel.d.ts.map +1 -0
- package/lib/models/OptionsModel.js +51 -0
- package/lib/models/OptionsModel.js.map +1 -0
- package/lib/models/SharedActionsInterface.d.ts +27 -0
- package/lib/models/SharedActionsInterface.d.ts.map +1 -0
- package/lib/models/SharedEventsInterface.d.ts +8 -0
- package/lib/models/SharedEventsInterface.d.ts.map +1 -0
- package/lib/models/SharedHelpersInterface.d.ts +26 -0
- package/lib/models/SharedHelpersInterface.d.ts.map +1 -0
- package/lib/models/SharedInitializerInterface.d.ts +6 -0
- package/lib/models/SharedInitializerInterface.d.ts.map +1 -0
- package/lib/models/SharedUtilsInterface.d.ts +10 -0
- package/lib/models/SharedUtilsInterface.d.ts.map +1 -0
- package/lib/models/StateInterface.d.ts +37 -0
- package/lib/models/StateInterface.d.ts.map +1 -0
- package/lib/models/StateInterface.js +9 -0
- package/lib/models/StateInterface.js.map +1 -0
- package/lib/models/ValidatorInterface.d.ts +74 -0
- package/lib/models/ValidatorInterface.d.ts.map +1 -0
- package/lib/models/ValidatorInterface.js +8 -0
- package/lib/models/ValidatorInterface.js.map +1 -0
- package/lib/parser.d.ts +13 -0
- package/lib/parser.d.ts.map +1 -0
- package/lib/parser.js +247 -0
- package/lib/parser.js.map +1 -0
- package/lib/props.d.ts +14 -0
- package/lib/props.d.ts.map +1 -0
- package/lib/props.js +120 -0
- package/lib/props.js.map +1 -0
- package/lib/utils.d.ts +34 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +182 -0
- package/lib/utils.js.map +1 -0
- package/lib/validators/AJV.d.ts +4 -0
- package/lib/validators/AJV.d.ts.map +1 -0
- package/lib/validators/AJV.js +109 -0
- package/lib/validators/AJV.js.map +1 -0
- package/lib/validators/DVR.d.ts +4 -0
- package/lib/validators/DVR.d.ts.map +1 -0
- package/lib/validators/DVR.js +108 -0
- package/lib/validators/DVR.js.map +1 -0
- package/lib/validators/JOI.d.ts +4 -0
- package/lib/validators/JOI.d.ts.map +1 -0
- package/lib/validators/JOI.js +58 -0
- package/lib/validators/JOI.js.map +1 -0
- package/lib/validators/VALIBOT.d.ts +4 -0
- package/lib/validators/VALIBOT.d.ts.map +1 -0
- package/lib/validators/VALIBOT.js +73 -0
- package/lib/validators/VALIBOT.js.map +1 -0
- package/lib/validators/VINEJS.d.ts +4 -0
- package/lib/validators/VINEJS.d.ts.map +1 -0
- package/lib/validators/VINEJS.js +64 -0
- package/lib/validators/VINEJS.js.map +1 -0
- package/lib/validators/VJF.d.ts +4 -0
- package/lib/validators/VJF.d.ts.map +1 -0
- package/lib/validators/VJF.js +92 -0
- package/lib/validators/VJF.js.map +1 -0
- package/lib/validators/YUP.d.ts +4 -0
- package/lib/validators/YUP.d.ts.map +1 -0
- package/lib/validators/YUP.js +73 -0
- package/lib/validators/YUP.js.map +1 -0
- package/lib/validators/ZOD.d.ts +4 -0
- package/lib/validators/ZOD.d.ts.map +1 -0
- package/lib/validators/ZOD.js +47 -0
- package/lib/validators/ZOD.js.map +1 -0
- package/package.json +183 -0
- package/src/ArrayMap.ts +165 -0
- package/src/Base.ts +1229 -0
- package/src/Bindings.ts +85 -0
- package/src/Field.ts +1034 -0
- package/src/Form.ts +264 -0
- package/src/Options.ts +91 -0
- package/src/State.ts +183 -0
- package/src/Validator.ts +177 -0
- package/src/compat.ts +104 -0
- package/src/composer.ts +82 -0
- package/src/index.ts +7 -0
- package/src/models/BaseInterface.ts +45 -0
- package/src/models/BindingsInterface.ts +25 -0
- package/src/models/FieldInterface.ts +97 -0
- package/src/models/FieldProps.ts +116 -0
- package/src/models/FormInterface.ts +203 -0
- package/src/models/OptionsInterface.ts +9 -0
- package/src/models/OptionsModel.ts +100 -0
- package/src/models/SharedActionsInterface.ts +30 -0
- package/src/models/SharedEventsInterface.ts +8 -0
- package/src/models/SharedHelpersInterface.ts +12 -0
- package/src/models/SharedInitializerInterface.ts +6 -0
- package/src/models/SharedUtilsInterface.ts +10 -0
- package/src/models/StateInterface.ts +44 -0
- package/src/models/ValidatorInterface.ts +88 -0
- package/src/parser.ts +327 -0
- package/src/props.ts +127 -0
- package/src/utils.ts +218 -0
- package/src/validators/AJV.ts +140 -0
- package/src/validators/DVR.ts +145 -0
- package/src/validators/JOI.ts +79 -0
- package/src/validators/VALIBOT.ts +70 -0
- package/src/validators/VINEJS.ts +83 -0
- package/src/validators/VJF.ts +123 -0
- package/src/validators/YUP.ts +90 -0
- package/src/validators/ZOD.ts +66 -0
- package/umd/MobxFormkit.umd.js +3093 -0
- package/umd/MobxFormkit.umd.js.map +1 -0
- package/umd/MobxFormkit.umd.min.js +2 -0
- package/umd/MobxFormkit.umd.min.js.map +1 -0
- package/umd/MobxFormkitComposer.umd.js +183 -0
- package/umd/MobxFormkitComposer.umd.js.map +1 -0
- package/umd/MobxFormkitComposer.umd.min.js +2 -0
- package/umd/MobxFormkitComposer.umd.min.js.map +1 -0
- package/umd/MobxFormkitValidatorAJV.umd.js +113 -0
- package/umd/MobxFormkitValidatorAJV.umd.js.map +1 -0
- package/umd/MobxFormkitValidatorAJV.umd.min.js +2 -0
- package/umd/MobxFormkitValidatorAJV.umd.min.js.map +1 -0
- package/umd/MobxFormkitValidatorDVR.umd.js +110 -0
- package/umd/MobxFormkitValidatorDVR.umd.js.map +1 -0
- package/umd/MobxFormkitValidatorDVR.umd.min.js +2 -0
- package/umd/MobxFormkitValidatorDVR.umd.min.js.map +1 -0
- package/umd/MobxFormkitValidatorJOI.umd.js +62 -0
- package/umd/MobxFormkitValidatorJOI.umd.js.map +1 -0
- package/umd/MobxFormkitValidatorJOI.umd.min.js +2 -0
- package/umd/MobxFormkitValidatorJOI.umd.min.js.map +1 -0
- package/umd/MobxFormkitValidatorVALIBOT.umd.js +8032 -0
- package/umd/MobxFormkitValidatorVALIBOT.umd.js.map +1 -0
- package/umd/MobxFormkitValidatorVALIBOT.umd.min.js +2 -0
- package/umd/MobxFormkitValidatorVALIBOT.umd.min.js.map +1 -0
- package/umd/MobxFormkitValidatorVINEJS.umd.js +11737 -0
- package/umd/MobxFormkitValidatorVINEJS.umd.js.map +1 -0
- package/umd/MobxFormkitValidatorVINEJS.umd.min.js +2 -0
- package/umd/MobxFormkitValidatorVINEJS.umd.min.js.map +1 -0
- package/umd/MobxFormkitValidatorVJF.umd.js +96 -0
- package/umd/MobxFormkitValidatorVJF.umd.js.map +1 -0
- package/umd/MobxFormkitValidatorVJF.umd.min.js +2 -0
- package/umd/MobxFormkitValidatorVJF.umd.min.js.map +1 -0
- package/umd/MobxFormkitValidatorYUP.umd.js +77 -0
- package/umd/MobxFormkitValidatorYUP.umd.js.map +1 -0
- package/umd/MobxFormkitValidatorYUP.umd.min.js +2 -0
- package/umd/MobxFormkitValidatorYUP.umd.min.js.map +1 -0
- package/umd/MobxFormkitValidatorZOD.umd.js +49 -0
- package/umd/MobxFormkitValidatorZOD.umd.js.map +1 -0
- package/umd/MobxFormkitValidatorZOD.umd.min.js +2 -0
- package/umd/MobxFormkitValidatorZOD.umd.min.js.map +1 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
# Guide for AI Agents
|
|
2
|
+
|
|
3
|
+
A concise guide for LLMs working with **MobX Formkit** — the reactive MobX form state management library.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Project Identity
|
|
8
|
+
|
|
9
|
+
| Field | Value |
|
|
10
|
+
| ---------------- | -------------------------------------------------------------------------------------- |
|
|
11
|
+
| **Package** | `mobx-formkit` |
|
|
12
|
+
| **NPM** | [npmjs.com/package/mobx-formkit](https://www.npmjs.com/package/mobx-formkit) |
|
|
13
|
+
| **GitHub** | [github.com/foxhound87/mobx-formkit](https://github.com/foxhound87/mobx-formkit) |
|
|
14
|
+
| **Docs** | [foxhound87.github.io/mobx-formkit](https://foxhound87.github.io/mobx-formkit/) |
|
|
15
|
+
| **Version** | 7.x (current: 7.1.0) |
|
|
16
|
+
| **Size** | ~8KB gzip (tree-shakeable) |
|
|
17
|
+
| **License** | MIT |
|
|
18
|
+
| **Dependencies** | MobX 5, 6 & 7 (peer: `^5.15.0 || ^6.0.0 || ^7.0.0`), lodash, React (optional) |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Philosophy
|
|
23
|
+
|
|
24
|
+
MobX Formkit is built on three core principles:
|
|
25
|
+
|
|
26
|
+
1. **Reactive by default** — Every field is a MobX observable. Form state (value, error, dirty, touched, etc.) is reactive. Your UI updates automatically with `observer()` — no manual `setState`, no change handlers to write.
|
|
27
|
+
|
|
28
|
+
2. **Composable by design** — Fields nest infinitely (objects, arrays, arrays of arrays). Forms compose via `composer()` for wizards and multi-step flows. `ArrayMap` preserves insertion order for dynamic lists.
|
|
29
|
+
|
|
30
|
+
3. **Plugin-driven validation** — 8 validation drivers (DVR, VJF, AJV, YUP, JOI, ZOD, VALIBOT, VINEJS) share a uniform lifecycle. Swap or combine them without changing field definitions.
|
|
31
|
+
|
|
32
|
+
The result: you define your form structure declaratively, and reactivity, validation, change tracking, and UI bindings are automatic.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 3. Architecture
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
┌──────────────────────────────────────────────────────┐
|
|
40
|
+
│ Form │
|
|
41
|
+
│ entry point — creates fields, runs validation, │
|
|
42
|
+
│ manages form-level computed state (isValid, error) │
|
|
43
|
+
├──────────────────────────────────────────────────────┤
|
|
44
|
+
│ fields: ArrayMap<Field> validator: Validator │
|
|
45
|
+
│ $hooks / $handlers options: Options │
|
|
46
|
+
│ state: State (initial/current props, struct, etc.) │
|
|
47
|
+
└──────────────────────┬───────────────────────────────┘
|
|
48
|
+
│ creates
|
|
49
|
+
┌──────────────────────┴───────────────────────────────┐
|
|
50
|
+
│ Field │
|
|
51
|
+
│ wraps each form input — value, validation, events │
|
|
52
|
+
├──────────────────────────────────────────────────────┤
|
|
53
|
+
│ $value (observable) errorSync / errorAsync │
|
|
54
|
+
│ $focused / $blurred / $touched / $changed │
|
|
55
|
+
│ $label / $placeholder / $disabled / $rules │
|
|
56
|
+
│ $hooks / $handlers $observers / $interceptors │
|
|
57
|
+
│ $converter / $input / $output │
|
|
58
|
+
│ fields: ArrayMap (nested sub-fields) │
|
|
59
|
+
└──────────────────────┬───────────────────────────────┘
|
|
60
|
+
│ shared via Base
|
|
61
|
+
┌──────────────────────┴───────────────────────────────┐
|
|
62
|
+
│ Base │
|
|
63
|
+
│ $(), get(), set(), validate(), submit(), add(), │
|
|
64
|
+
│ del(), update(), clear(), reset(), each(), map() │
|
|
65
|
+
│ execHook(), execHandler() │
|
|
66
|
+
│ onClear(), onReset(), onSubmit(), onAdd(), onDel() │
|
|
67
|
+
└──────────────────────────────────────────────────────┘
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Supporting Classes
|
|
71
|
+
|
|
72
|
+
| Class | Role |
|
|
73
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| **`Validator`** | Runs validation across all enabled drivers. Manages `validate()`, `validateField()`, `validateRelatedFields()`. |
|
|
75
|
+
| **`Options`** | 40+ form-level configuration flags (validation triggers, debounce, strict modes, converters, soft delete, etc.). |
|
|
76
|
+
| **`Bindings`** | Maps field props → component props via rewriters (simple key mapping) or templates (function-based). |
|
|
77
|
+
| **`State`** | Holds initial, current, and struct data. Bridges field → form during initialization. |
|
|
78
|
+
| **`ArrayMap`** | Ordered key-value collection (backed by observable array). Supports `move(from, to)` for sortable lists. |
|
|
79
|
+
| **`composer()`** | Utility function for orchestrating multiple Form instances (wizards, multi-step flows). |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 4. Key Concepts
|
|
84
|
+
|
|
85
|
+
### 4.1 Form
|
|
86
|
+
|
|
87
|
+
The entry point. Instantiated with field definitions + config:
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
const form = new Form(
|
|
91
|
+
{ fields: [{ name: 'email', label: 'Email', rules: 'required|email' }] },
|
|
92
|
+
{ plugins: { dvr: dvr({ package: validatorjs }) }, hooks: { onSuccess(form) { ... } } }
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Constructor signature:**
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Form(setup: FieldsDefinitions, config: FormConfig)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Config key | Type | Purpose |
|
|
103
|
+
| ---------- | --------------------------------------------------- | ------------------------------- | --------------------------- |
|
|
104
|
+
| `plugins` | `{ dvr?, vjf?, ajv?, yup?, zod?, joi?, valibot?, vinejs? }` | Validation drivers |
|
|
105
|
+
| `hooks` | `{ onInit?, onChange?, onSuccess?, onError?, ... }` | Lifecycle callbacks |
|
|
106
|
+
| `handlers` | `{ onSubmit?, onKeyDown?, ... }` | Custom event handlers (curried) |
|
|
107
|
+
| `options` | `OptionsModel` | Form behavior flags |
|
|
108
|
+
| `bindings` | `Record<string, Rewriter | Template>` | Custom bindings for UI libs |
|
|
109
|
+
| `extra` | `Record<string, any>` | Arbitrary metadata |
|
|
110
|
+
| `name` | `string` | Form name for debugging |
|
|
111
|
+
|
|
112
|
+
### 4.2 Field
|
|
113
|
+
|
|
114
|
+
Represents a single form input. Observable properties drive UI reactivity:
|
|
115
|
+
|
|
116
|
+
**Core props:** `value`, `initial`, `default`, `label`, `placeholder`, `type`, `bindings`, `extra`, `options`, `related`, `rules`, `validators`, `disabled`, `deleted`, `autoFocus`, `inputMode`, `ref`, `nullable`, `autoComplete`
|
|
117
|
+
|
|
118
|
+
**Computed state:** `error`, `isValid`, `isDirty`, `isPristine`, `isEmpty`, `isDefault`, `hasError`, `focused`, `blurred`, `touched`, `validatedValue`, `checked`, `changed`, `validating`, `submitting`, `clearing`, `resetting`, `actionRunning`
|
|
119
|
+
|
|
120
|
+
**Converters:** `$converter` (value transform), `$input` (apply on input), `$output` (apply on output via `values()`)
|
|
121
|
+
|
|
122
|
+
### 4.3 Nested Fields
|
|
123
|
+
|
|
124
|
+
Fields can contain sub-fields — infinitely nestable. Accessed via dot notation:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// Access
|
|
128
|
+
form.$("address"); // Field (container)
|
|
129
|
+
form.$("address.city"); // Field (nested)
|
|
130
|
+
form.values(); // { address: { city: '...', zip: '...' } }
|
|
131
|
+
|
|
132
|
+
// Arrays
|
|
133
|
+
form.$("members[0]"); // Field
|
|
134
|
+
form.$("members[0].name"); // nested inside array element
|
|
135
|
+
form.$("members[].name"); // applies to all array elements
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**For nested fields and arrays of objects, prefer the separated prop mode** — define the structure via `struct` and split props across parallel objects. This keeps definitions clean and avoids deeply nested unified objects:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const fields = {
|
|
142
|
+
struct: ["address.city", "address.zip", "members[].name", "members[].email"],
|
|
143
|
+
labels: {
|
|
144
|
+
"address.city": "City",
|
|
145
|
+
"address.zip": "ZIP Code",
|
|
146
|
+
"members[].name": "Name",
|
|
147
|
+
"members[].email": "Email",
|
|
148
|
+
},
|
|
149
|
+
rules: {
|
|
150
|
+
"address.city": "required",
|
|
151
|
+
"address.zip": "required|digits:5",
|
|
152
|
+
"members[].name": "required",
|
|
153
|
+
},
|
|
154
|
+
values: {
|
|
155
|
+
address: { city: "Rome", zip: "00100" },
|
|
156
|
+
members: [{ name: "John", email: "john@test.com" }],
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**ArrayMap** provides ordered key-value storage with array-like operations (`move(from, to)` for sortable lists) while exposing the full Map API.
|
|
162
|
+
|
|
163
|
+
### 4.4 Computed (Functional) Props
|
|
164
|
+
|
|
165
|
+
Any field prop can be a function that receives `{ field, form }` and returns a reactive value:
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
const fields = [
|
|
169
|
+
{
|
|
170
|
+
name: "total",
|
|
171
|
+
value: ({ field, form }) =>
|
|
172
|
+
form
|
|
173
|
+
.$("items")
|
|
174
|
+
.values()
|
|
175
|
+
.reduce((sum, item) => sum + item.price * item.qty, 0),
|
|
176
|
+
disabled: ({ field, form }) => !form.$("items").values().length,
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Computed props re-evaluate automatically when their dependencies (MobX observables they read) change. Available on: `value`, `label`, `placeholder`, `disabled`, `rules`, `related`, `bindings`, `extra`, `options`, `deleted`, `autoFocus`, `inputMode`, and all `computed` fields.
|
|
182
|
+
|
|
183
|
+
### 4.5 Hooks
|
|
184
|
+
|
|
185
|
+
Lifecycle callbacks. You define them in the form config or in field definitions:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
const hooks = {
|
|
189
|
+
// Form lifecycle
|
|
190
|
+
onInit(form) { ... }, // After form creation
|
|
191
|
+
onChange(form) { ... }, // On any field value change
|
|
192
|
+
|
|
193
|
+
// Submit lifecycle
|
|
194
|
+
onSuccess(form) { ... }, // Validation passed
|
|
195
|
+
onError(form) { ... }, // Validation failed
|
|
196
|
+
onSubmit(form) { ... }, // Before validation
|
|
197
|
+
|
|
198
|
+
// Field lifecycle
|
|
199
|
+
onChange(field, event) { ... }, // Field value changed
|
|
200
|
+
onBlur(field, event) { ... }, // Field lost focus
|
|
201
|
+
onFocus(field, event) { ... }, // Field gained focus
|
|
202
|
+
onClear(field) { ... }, // Field cleared
|
|
203
|
+
onReset(field) { ... }, // Field reset
|
|
204
|
+
onAdd(field) { ... }, // Field added dynamically
|
|
205
|
+
onDel(field) { ... }, // Field removed dynamically
|
|
206
|
+
};
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Important:** Hooks receive `(field, event)` when triggered by event handlers, but only `(field)` when called directly from actions.
|
|
210
|
+
|
|
211
|
+
### 4.6 Handlers
|
|
212
|
+
|
|
213
|
+
Event handler functions passed to DOM elements via `field.bind()`. Custom handlers are **curried**: `(field) => (event) => {}`:
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
const handlers = {
|
|
217
|
+
onKeyDown: (field) => (e) => {
|
|
218
|
+
if (e.key === "Enter") field.submit();
|
|
219
|
+
},
|
|
220
|
+
onSubmit: (form) => (e) => {
|
|
221
|
+
e.preventDefault();
|
|
222
|
+
console.log("submitted");
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Built-in handlers on Field: `onChange` (aliased as `onSync`), `onBlur`, `onFocus`, `onKeyDown`, `onKeyUp`, `onToggle`, `onDrop`.
|
|
228
|
+
|
|
229
|
+
Built-in handlers on Form/Base: `onSubmit`, `onClear`, `onReset`, `onAdd`, `onDel`.
|
|
230
|
+
|
|
231
|
+
### 4.7 Validation Drivers
|
|
232
|
+
|
|
233
|
+
Eight plugins sharing a uniform lifecycle. Enable one or more:
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
import dvr from "mobx-formkit/lib/validators/DVR";
|
|
237
|
+
import vjf from "mobx-formkit/lib/validators/VJF";
|
|
238
|
+
import yupPlugin from "mobx-formkit/lib/validators/YUP";
|
|
239
|
+
import joiPlugin from "mobx-formkit/lib/validators/JOI";
|
|
240
|
+
import zodPlugin from "mobx-formkit/lib/validators/ZOD";
|
|
241
|
+
import ajv from "mobx-formkit/lib/validators/AJV";
|
|
242
|
+
import valibotPlugin from "mobx-formkit/lib/validators/VALIBOT";
|
|
243
|
+
import vinejsPlugin from "mobx-formkit/lib/validators/VINEJS";
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
| Driver | Rules Format | Async | Extend | Best For |
|
|
247
|
+
| --------- | ------------------------------------ | ----- | ------ | ------------------------------- |
|
|
248
|
+
| **DVR** | String rules (`'required\|email'`) | ✅ | ✅ | Simple forms, quick setup |
|
|
249
|
+
| **VJF** | Custom functions `() => [bool, msg]` | ✅ | ✅ | Full control, complex logic |
|
|
250
|
+
| **AJV** | JSON Schema | ✅ | ✅ | API compatibility, schema-first |
|
|
251
|
+
| **YUP** | `y.string().required()` | ❌ | ❌ | Modern JS/TS |
|
|
252
|
+
| **JOI** | `j.string().required()` | ❌ | ❌ | Enterprise, rich rules |
|
|
253
|
+
| **ZOD** | `z.string().min(3)` | ❌ | ❌ | TypeScript-native |
|
|
254
|
+
| **VALIBOT** | `v.string(), v.minLength(3)` | ❌ | ✅ | Sync, tiny TS-first schemas |
|
|
255
|
+
| **VINEJS** | `vine.string().minLength(3)` | ✅* | ✅ | Sync rules, async API |
|
|
256
|
+
|
|
257
|
+
\* VineJS `validate()` is async-by-design at the API level (even for synchronous
|
|
258
|
+
rules) — the driver consumes the MRF async contract (`setValidationAsyncData` +
|
|
259
|
+
`promises`), mirroring the YUP driver.
|
|
260
|
+
|
|
261
|
+
- **ZOD** driver supports **zod v3.25+ and v4** (peer `^3.25.0 || ^4.0.0`,
|
|
262
|
+
optional peer; devDep in repo). Driver relies only on `safeParse` + `error.format()`,
|
|
263
|
+
stable across both majors.
|
|
264
|
+
|
|
265
|
+
**Example — enable DVR + VJF together:**
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
const plugins = {
|
|
269
|
+
dvr: dvr({ package: validatorjs }),
|
|
270
|
+
vjf: vjf(),
|
|
271
|
+
};
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Control order via `options.validationPluginsOrder: ['vjf', 'dvr']`.
|
|
275
|
+
|
|
276
|
+
### 4.8 Bindings
|
|
277
|
+
|
|
278
|
+
`field.bind()` returns props for your UI component. Two strategies:
|
|
279
|
+
|
|
280
|
+
**Default bindings** (text input → `{ id, name, type, value, onChange, onBlur, ... }`):
|
|
281
|
+
|
|
282
|
+
```jsx
|
|
283
|
+
<input {...form.$("email").bind()} />
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Rewriters** — simple key mapping for UI libraries:
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
const bindings = {
|
|
290
|
+
MaterialTextField: { label: "floatingLabelText", placeholder: "hintText" },
|
|
291
|
+
};
|
|
292
|
+
// Then: <TextField {...form.$('email').bind()} />
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Templates** — full control with a function:
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
const bindings = {
|
|
299
|
+
CustomSelect: ({ field, form, keys, props, $try }) => ({
|
|
300
|
+
value: field.value,
|
|
301
|
+
onChange: (e, val) => (field.value = val),
|
|
302
|
+
error: field.error,
|
|
303
|
+
options: field.extra.options,
|
|
304
|
+
}),
|
|
305
|
+
};
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### 4.9 Options
|
|
309
|
+
|
|
310
|
+
40+ configuration flags. Set via `options` in the form config:
|
|
311
|
+
|
|
312
|
+
| Group | Key Options |
|
|
313
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
314
|
+
| **Validation triggers** | `validateOnInit` (true), `validateOnBlur` (true), `validateOnChange` (false), `validateOnSubmit` (true), `validateOnClear` (false), `validateOnReset` (false) |
|
|
315
|
+
| **Error display** | `showErrorsOnInit` (false), `showErrorsOnBlur` (true), `showErrorsOnChange` (true), `showErrorsOnSubmit` (true), `showErrorsOnClear` (false), `showErrorsOnReset` (true) |
|
|
316
|
+
| **Debounce** | `validationDebounceWait` (250ms), `validationDebounceOptions` `{ leading: false, trailing: true }` |
|
|
317
|
+
| **Strict modes** | `strictSelect` (true), `strictDelete` (true), `strictSet` (false), `strictUpdate` (false) |
|
|
318
|
+
| **Converters** | `applyInputConverterOnInit` (true), `applyInputConverterOnSet` (true), `applyInputConverterOnUpdate` (true) |
|
|
319
|
+
| **Retrieval** | `retrieveOnlyDirtyFieldsValues` (false), `retrieveOnlyEnabledFieldsValues` (false), `softDelete` (false), `retrieveNullifiedEmptyStrings` (false), `removeNullishValuesInArrays` (false) |
|
|
320
|
+
| **Validation** | `stopValidationOnError` (false), `resetValidationBeforeValidate` (true), `validateDisabledFields` (false), `validateDeletedFields` (false), `validatePristineFields` (true), `validationPluginsOrder` (undefined) |
|
|
321
|
+
| **Other** | `submitThrowsError` (true), `defaultGenericError` (null), `fallback` (true), `fallbackValue` (''), `autoTrimValue` (false), `autoParseNumbers` (false), `bubbleUpErrorMessages` (false), `preserveDeletedFieldsValues` (false) |
|
|
322
|
+
|
|
323
|
+
Options can be overridden **per-field** via `field.$options`:
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
const fields = [
|
|
327
|
+
{
|
|
328
|
+
name: "email",
|
|
329
|
+
options: { validateOnChange: true, validationDebounceWait: 500 },
|
|
330
|
+
},
|
|
331
|
+
];
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 4.10 Field Definition Modes
|
|
335
|
+
|
|
336
|
+
**Unified mode** — all props in one object per field:
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
const fields = [
|
|
340
|
+
{
|
|
341
|
+
name: "email",
|
|
342
|
+
label: "Email",
|
|
343
|
+
rules: "required|email",
|
|
344
|
+
value: "test@test.com",
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Separated mode** — props split across parallel objects:
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const fields = {
|
|
353
|
+
struct: ["email"],
|
|
354
|
+
values: { email: "test@test.com" },
|
|
355
|
+
labels: { email: "Email" },
|
|
356
|
+
rules: { email: "required|email" },
|
|
357
|
+
};
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Mixed mode** — unified overrides separated. Any combination works.
|
|
361
|
+
|
|
362
|
+
### 4.11 Field Converters
|
|
363
|
+
|
|
364
|
+
Three levels of value transformation:
|
|
365
|
+
|
|
366
|
+
- **`$converter`** — transforms the raw value whenever it's set (e.g., uppercase, trim)
|
|
367
|
+
- **`$input`** — applied when value comes from user input (via `sync`, `onChange`, `update`, `set`)
|
|
368
|
+
- **`$output`** — applied when value is read via `values()`, `flatMapValues`, or `get('value')`
|
|
369
|
+
|
|
370
|
+
Apply via field definition:
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
{
|
|
374
|
+
name: 'email',
|
|
375
|
+
input: (val) => val.toLowerCase().trim(),
|
|
376
|
+
output: (val) => val ? `***@${val.split('@')[1]}` : '',
|
|
377
|
+
converter: (val) => val?.toUpperCase(),
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 5. Core API Reference
|
|
384
|
+
|
|
385
|
+
### 5.1 Field Selection
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
form.$("email"); // typed: Field<EmailType>
|
|
389
|
+
form.$("address.city"); // nested by dot notation
|
|
390
|
+
form.$("members[0].name"); // array by index
|
|
391
|
+
form.$("members[].name"); // returns Field for first element
|
|
392
|
+
form.select("email"); // untyped variant
|
|
393
|
+
form.has("email"); // boolean check
|
|
394
|
+
form.$("email").$("nestedField"); // relative selection from a field
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 5.2 Getting / Setting Values
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
// Single
|
|
401
|
+
form.$("email").value; // get
|
|
402
|
+
form.$("email").value = "new@test.com"; // set via setter
|
|
403
|
+
form.$("email").set("new@test.com"); // set via method
|
|
404
|
+
form.set("email", "new@test.com"); // set via form
|
|
405
|
+
|
|
406
|
+
// Bulk
|
|
407
|
+
form.values(); // { email: '...', address: { city: '...' } }
|
|
408
|
+
form.update({ email: "new@test.com", address: { city: "New York" } }); // nested
|
|
409
|
+
form.get("value"); // same as values()
|
|
410
|
+
form.get("error"); // { email: '...', address: { city: '...' } }
|
|
411
|
+
form.errors(); // same
|
|
412
|
+
form.get(["value", "error"]); // { email: { value: '...', error: '...' } }
|
|
413
|
+
form.$("email").get(["value", "error"]); // { value: '...', error: '...' }
|
|
414
|
+
|
|
415
|
+
// Field-level
|
|
416
|
+
field.value; // get
|
|
417
|
+
field.value = "new"; // set
|
|
418
|
+
field.initial; // original value
|
|
419
|
+
field.default; // default value
|
|
420
|
+
field.validatedValue; // value after converters applied
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### 5.3 Validation
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
// Validate all fields
|
|
427
|
+
await form.validate();
|
|
428
|
+
await form.validate({ showErrors: true, related: true });
|
|
429
|
+
|
|
430
|
+
// Submit (validate + hooks)
|
|
431
|
+
await form.submit(); // calls onSuccess or onError
|
|
432
|
+
await form.submit({ onSuccess, onError }, { validate: true });
|
|
433
|
+
|
|
434
|
+
// Single field
|
|
435
|
+
await form.$("email").validate();
|
|
436
|
+
await form.$("email").validate({ related: true }); // validates related fields too
|
|
437
|
+
|
|
438
|
+
// Manual error
|
|
439
|
+
form.$("email").invalidate("Custom error");
|
|
440
|
+
form.invalidate("Form-level error");
|
|
441
|
+
|
|
442
|
+
// Control error display
|
|
443
|
+
form.showErrors(true); // show all errors
|
|
444
|
+
form.$("email").showErrors(false); // hide specific field error
|
|
445
|
+
form.check(); // check without displaying errors
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### 5.4 Dynamic Fields
|
|
449
|
+
|
|
450
|
+
```javascript
|
|
451
|
+
form.add({ name: "newField", value: "test" }); // add a field
|
|
452
|
+
form.add({ name: "guest", fields: [{ name: "email" }] }); // add nested field
|
|
453
|
+
form.del("oldField"); // remove a field
|
|
454
|
+
form.$("members").add({ name: "John" }); // add to array container
|
|
455
|
+
form.$("members").del("0"); // remove array element
|
|
456
|
+
form.$("members").move(0, 2); // reorder (sortable lists)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### 5.5 Clear / Reset
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
form.clear(); // clear all values + reset validation
|
|
463
|
+
form.reset(); // restore initial/default values
|
|
464
|
+
form.$("email").clear(); // single field
|
|
465
|
+
form.$("email").reset();
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### 5.6 State Checks
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
form.isValid; // all fields valid (every)
|
|
472
|
+
form.isDirty; // any field changed (some)
|
|
473
|
+
form.isPristine; // no field changed (every)
|
|
474
|
+
form.isEmpty; // all fields empty
|
|
475
|
+
form.hasError; // any field has error (some)
|
|
476
|
+
form.error; // first error message (requires bubbleUpErrorMessages)
|
|
477
|
+
form.focused; // any field focused
|
|
478
|
+
form.touched; // any field blurred at least once
|
|
479
|
+
form.disabled; // all fields disabled (every)
|
|
480
|
+
form.changed; // change count (number)
|
|
481
|
+
form.submitted; // submission count (number)
|
|
482
|
+
form.validating; // validation in progress (boolean)
|
|
483
|
+
form.submitting; // submission in progress (boolean)
|
|
484
|
+
form.size; // number of top-level fields
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### 5.7 Event Wiring
|
|
488
|
+
|
|
489
|
+
```javascript
|
|
490
|
+
// In JSX — built-in handlers
|
|
491
|
+
<form onSubmit={form.onSubmit}>
|
|
492
|
+
<input {...form.$("email").bind()} />
|
|
493
|
+
<button onClick={form.onClear}>Clear</button>
|
|
494
|
+
<button onClick={form.onReset}>Reset</button>
|
|
495
|
+
</form>
|
|
496
|
+
|
|
497
|
+
// The bind() output includes: id, name, type, value, checked,
|
|
498
|
+
// label, placeholder, disabled, autoComplete, onChange, onBlur,
|
|
499
|
+
// onFocus, autoFocus, inputMode, onKeyUp, onKeyDown
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 5.8 TypeScript
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import { Form, Field } from 'mobx-formkit';
|
|
506
|
+
import type { PathsOf, FieldDefinition } from 'mobx-formkit';
|
|
507
|
+
|
|
508
|
+
// Typed form
|
|
509
|
+
interface Profile {
|
|
510
|
+
username: string;
|
|
511
|
+
email: string;
|
|
512
|
+
address: { city: string; zip: string };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const form = new Form<Profile>({ fields: [...] });
|
|
516
|
+
form.$('username'); // Field<string>
|
|
517
|
+
form.$('address.city'); // Field<string> (with PathsOf autocomplete)
|
|
518
|
+
|
|
519
|
+
// Typed field definitions
|
|
520
|
+
const fields: Record<string, FieldDefinition> = [
|
|
521
|
+
{ name: 'email', rules: 'required|email' },
|
|
522
|
+
];
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### 5.9 Composer (Multi-Form)
|
|
526
|
+
|
|
527
|
+
```javascript
|
|
528
|
+
import { composer } from "mobx-formkit/lib/composer";
|
|
529
|
+
|
|
530
|
+
const wizard = composer({ step1: form1, step2: form2 });
|
|
531
|
+
wizard.validate(); // validates all forms
|
|
532
|
+
wizard.submit(); // submits all forms
|
|
533
|
+
wizard.clear(); // clears all forms
|
|
534
|
+
wizard.valid(); // all forms valid?
|
|
535
|
+
wizard.error(); // any form has error?
|
|
536
|
+
wizard.get("value"); // { step1: {...}, step2: {...} }
|
|
537
|
+
wizard.select("step1"); // individual form
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### 5.10 Observers & Interceptors
|
|
541
|
+
|
|
542
|
+
MobX `observe`/`intercept` on any field prop:
|
|
543
|
+
|
|
544
|
+
```javascript
|
|
545
|
+
const fields = [{
|
|
546
|
+
name: 'email',
|
|
547
|
+
observers: [{
|
|
548
|
+
prop: 'value',
|
|
549
|
+
call: ({ change, field, form }) =>
|
|
550
|
+
console.log('Email changed:', change.newValue),
|
|
551
|
+
}],
|
|
552
|
+
interceptors: [{
|
|
553
|
+
prop: 'value',
|
|
554
|
+
call: ({ change }) => change.newValue?.length > 50 ? null : change,
|
|
555
|
+
}],
|
|
556
|
+
}];
|
|
557
|
+
|
|
558
|
+
// Or imperatively:
|
|
559
|
+
field.observe(({ change, field, form }) => { ... });
|
|
560
|
+
field.intercept(({ change }) => change);
|
|
561
|
+
form.intercept({ prop: 'value', path: 'email', call: ({ change }) => change });
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 6. Field Definition Reference
|
|
567
|
+
|
|
568
|
+
All properties you can define per field:
|
|
569
|
+
|
|
570
|
+
| Prop | Type | Description |
|
|
571
|
+
| --------------- | -------------------------- | ---------------------------------------------------------------------------- |
|
|
572
|
+
| `name` | `string` | Field name (overrides key) |
|
|
573
|
+
| `value` | `any` | Field value |
|
|
574
|
+
| `computed` | `({ field, form }) => any` | Reactive computed value |
|
|
575
|
+
| `label` | `string \| function` | Display label |
|
|
576
|
+
| `placeholder` | `string \| function` | Placeholder text |
|
|
577
|
+
| `type` | `string` | Input type (`text`, `checkbox`, `password`, `file`, `number`, etc.) |
|
|
578
|
+
| `rules` | `string` | Validation rules (DVR: `'required\|email'`, VJF/AJV/YUP/JOI/ZOD/VALIBOT/VINEJS: per-driver) |
|
|
579
|
+
| `validators` | `function[]` | Custom validation functions (VJF) |
|
|
580
|
+
| `related` | `string[]` | Re-validate these field paths when this field changes |
|
|
581
|
+
| `disabled` | `boolean \| function` | Disabled state |
|
|
582
|
+
| `deleted` | `boolean \| function` | Soft-delete flag (requires `softDelete: true`) |
|
|
583
|
+
| `default` | `any` | Default value (used on reset) |
|
|
584
|
+
| `initial` | `any` | Initial value (set on creation) |
|
|
585
|
+
| `fields` | `array` | Nested field definitions |
|
|
586
|
+
| `bindings` | `string` | Name of binding to use (from `form.bindings`) |
|
|
587
|
+
| `extra` | `any` | Arbitrary metadata |
|
|
588
|
+
| `options` | `OptionsModel` | Per-field option overrides |
|
|
589
|
+
| `hooks` | `object` | Field-level hooks |
|
|
590
|
+
| `handlers` | `object` | Field-level handlers |
|
|
591
|
+
| `autoFocus` | `boolean \| function` | Auto-focus on mount |
|
|
592
|
+
| `inputMode` | `string \| function` | Mobile keyboard mode |
|
|
593
|
+
| `ref` | `any` | Ref callback/object |
|
|
594
|
+
| `nullable` | `boolean` | Allow null value |
|
|
595
|
+
| `autoComplete` | `string` | Autocomplete attribute |
|
|
596
|
+
| `input` | `function` | Input converter (user → internal) |
|
|
597
|
+
| `output` | `function` | Output converter (internal → API) |
|
|
598
|
+
| `converter` | `function` | Transform on set |
|
|
599
|
+
| `observers` | `array` | MobX observers |
|
|
600
|
+
| `interceptors` | `array` | MobX interceptors |
|
|
601
|
+
| `class` | `class` | Custom Field class for this field |
|
|
602
|
+
| `validatedWith` | `string` | Which prop to validate (`'value'` default) |
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
## 7. Best Practices
|
|
607
|
+
|
|
608
|
+
### Validation
|
|
609
|
+
|
|
610
|
+
- **Prefer hooks over raw handlers** for most validation logic. Define `onSuccess` and `onError` in the form config.
|
|
611
|
+
- **VJF is best for complex logic** — regex, API checks, cross-field rules, async validation.
|
|
612
|
+
- **DVR is best for simple forms** — string rules like `'required|email|between:5,25'` are concise.
|
|
613
|
+
- **Use `related`** to auto-revalidate dependent fields (e.g., `passwordConfirm` when `password` changes).
|
|
614
|
+
- **`validateOnChange` is false by default** — enable it per-field when needed, or use `validateOnBlur` (default: true).
|
|
615
|
+
- **Set `validationDebounceWait`** higher (e.g., 500ms) for expensive async validation.
|
|
616
|
+
|
|
617
|
+
### Performance
|
|
618
|
+
|
|
619
|
+
- **Use `retrieveOnlyDirtyFieldsValues: true`** to only submit changed fields.
|
|
620
|
+
- **Use `retrieveOnlyEnabledFieldsValues: true`** to exclude disabled fields from API payloads.
|
|
621
|
+
- **Keep forms flat where possible** — deeply nested fields add overhead.
|
|
622
|
+
- **Use `bubbleUpErrorMessages: true`** for a quick `form.error` string.
|
|
623
|
+
- **Lazy validation** — disable `validateOnInit` for forms with many fields that aren't immediately visible.
|
|
624
|
+
|
|
625
|
+
### Reactivity
|
|
626
|
+
|
|
627
|
+
- **Wrap components with `observer()`** from `mobx-react` to auto-render on field changes.
|
|
628
|
+
- **Computed props** `({ field, form }) => ...` are reactive — use them instead of manual `reaction()`.
|
|
629
|
+
- **Don't spread `field.bind()` deeply** — the spread is reactive as long as the component is observed.
|
|
630
|
+
- **Use `field.validatedValue`** when converters are applied and you need the processed value.
|
|
631
|
+
|
|
632
|
+
### Structure
|
|
633
|
+
|
|
634
|
+
- **Unified mode** is simplest for flat, simple forms. **For nested fields and arrays of objects, prefer separated mode** — define paths via `struct` and props via parallel objects. This avoids deeply nested unified objects and makes definitions cleaner.
|
|
635
|
+
- **Use `struct`** in separated mode to explicitly define the shape when `fallback: false`.
|
|
636
|
+
- **Dynamic arrays**: use `form.$('items').add({ name: 'newItem' })` and `form.$('items').del('0')`.
|
|
637
|
+
- **Sortable lists**: `form.$('items').move(oldIndex, newIndex)` — integrated with drag-and-drop.
|
|
638
|
+
- **Nested forms**: use `composer()` for independent form instances in wizards.
|
|
639
|
+
|
|
640
|
+
### TypeScript
|
|
641
|
+
|
|
642
|
+
- **Always pass the data interface** as a generic: `new Form<MyInterface>(...)`.
|
|
643
|
+
- **Use `PathsOf<F>`** for type-safe nested field access.
|
|
644
|
+
- **Define fields with `Record<string, FieldDefinition>`** for autocomplete.
|
|
645
|
+
- **Custom Field classes** can extend `Field` for reusable field behavior.
|
|
646
|
+
|
|
647
|
+
### Gotchas
|
|
648
|
+
|
|
649
|
+
| Gotcha | Detail |
|
|
650
|
+
| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
651
|
+
| **Handlers are curried** | Custom handlers: `(field) => (e) => {}`, not `(field, e) => {}`. |
|
|
652
|
+
| **`submitThrowsError: true`** | Default — wrap `submit()` in try/catch or use validation hooks. |
|
|
653
|
+
| **`onSubmit` hook** receives form, not field. `onSuccess`/`onError` receive form. |
|
|
654
|
+
| **`field.bind()`** sets `field.$ref` after render. Access in `useEffect` or similar. |
|
|
655
|
+
| **`softDelete`** requires `softDelete: true` option; deleted fields are excluded from `values()`. |
|
|
656
|
+
| **`fallback: false`** + missing fields from struct won't be auto-created. Useful for strict schemas. |
|
|
657
|
+
| **`strictSelect: true`** (default) throws on invalid field paths — catch or disable for dynamic paths. |
|
|
658
|
+
| **`fallbackValue`** controls the default empty value (`''` by default; can be `null`, `undefined`, `0`). |
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## 8. Quick Patterns
|
|
663
|
+
|
|
664
|
+
### Simplest possible form
|
|
665
|
+
|
|
666
|
+
```javascript
|
|
667
|
+
const form = new Form(
|
|
668
|
+
{
|
|
669
|
+
fields: [{ name: "email", rules: "required|email" }],
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
plugins: { dvr: dvr({ package: validatorjs }) },
|
|
673
|
+
hooks: { onSuccess: (f) => console.log(f.values()) },
|
|
674
|
+
},
|
|
675
|
+
);
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Field with computed value
|
|
679
|
+
|
|
680
|
+
```javascript
|
|
681
|
+
{ name: 'fullName', value: ({ field, form }) =>
|
|
682
|
+
`${form.$('firstName').value} ${form.$('lastName').value}`.trim()
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Custom error handling
|
|
687
|
+
|
|
688
|
+
```javascript
|
|
689
|
+
try {
|
|
690
|
+
await form.submit();
|
|
691
|
+
} catch {
|
|
692
|
+
console.log("Validation failed:", form.errors());
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### Per-field validation on change
|
|
697
|
+
|
|
698
|
+
```javascript
|
|
699
|
+
{ name: 'email', options: { validateOnChange: true } }
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### Async validation (VJF)
|
|
703
|
+
|
|
704
|
+
```javascript
|
|
705
|
+
{ name: 'username', validators: [({ field }) =>
|
|
706
|
+
fetch(`/api/check-username?q=${field.value}`)
|
|
707
|
+
.then(r => r.json())
|
|
708
|
+
.then(({ available }) => [available, 'Username taken'])
|
|
709
|
+
]}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### Array with dynamic items
|
|
713
|
+
|
|
714
|
+
```javascript
|
|
715
|
+
const fields = {
|
|
716
|
+
struct: ["items[].name", "items[].qty"],
|
|
717
|
+
rules: {
|
|
718
|
+
"items[].name": "required",
|
|
719
|
+
"items[].qty": "required|numeric",
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
// Add: form.$('items').add({ name: 'New', qty: 1 })
|
|
723
|
+
// Remove: form.$('items').del('0')
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Custom binding for MUI TextField
|
|
727
|
+
|
|
728
|
+
```javascript
|
|
729
|
+
const bindings = {
|
|
730
|
+
MuiTextField: {
|
|
731
|
+
id: "id",
|
|
732
|
+
name: "name",
|
|
733
|
+
type: "type",
|
|
734
|
+
value: "value",
|
|
735
|
+
label: "label",
|
|
736
|
+
placeholder: "placeholder",
|
|
737
|
+
disabled: "disabled",
|
|
738
|
+
autoFocus: "autoFocus",
|
|
739
|
+
onChange: "onChange",
|
|
740
|
+
onBlur: "onBlur",
|
|
741
|
+
onFocus: "onFocus",
|
|
742
|
+
error: "error",
|
|
743
|
+
helperText: "error", // MUI specific
|
|
744
|
+
},
|
|
745
|
+
};
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## 9. Related Repositories
|
|
751
|
+
|
|
752
|
+
| Repo | Purpose |
|
|
753
|
+
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
754
|
+
| [mobx-formkit-demo](https://github.com/foxhound87/mobx-formkit-demo) | Live demo app with React components |
|
|
755
|
+
| [mobx-formkit-devtools](https://github.com/foxhound87/mobx-formkit-devtools) | DevTools panel for debugging |
|
|
756
|
+
| [mobx-formkit--docs](https://foxhound87.github.io/mobx-formkit/) | This documentation site |
|
|
757
|
+
| [skills](https://github.com/foxhound87/skills) | AI skill files for Cursor, Windsurf, Claude Code, Codebuff |
|
|
758
|
+
|
|
759
|
+
---
|
|
760
|
+
|
|
761
|
+
> **For LLMs:** When helping users with mobx-formkit, always check which validation plugin is being used — patterns differ significantly between DVR/VJF/AJV/YUP/JOI/ZOD/VALIBOT/VINEJS. Prefer `computed` props over manual reactivity. Always wrap components with `observer()`. Use `field.bind()` instead of manual prop wiring. Custom handlers are **curried** `(field) => (e) =>`.
|