@inl0re/blnk 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/LICENSE +21 -0
- package/README.md +148 -0
- package/README.ru.md +476 -0
- package/dist/core/createFormControl.d.ts +3 -0
- package/dist/core/createFormControl.d.ts.map +1 -0
- package/dist/core/fieldArrayState.d.ts +23 -0
- package/dist/core/fieldArrayState.d.ts.map +1 -0
- package/dist/core/fieldRegistry.d.ts +9 -0
- package/dist/core/fieldRegistry.d.ts.map +1 -0
- package/dist/core/formState.d.ts +13 -0
- package/dist/core/formState.d.ts.map +1 -0
- package/dist/core/store.d.ts +4 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/subscriptionManager.d.ts +8 -0
- package/dist/core/subscriptionManager.d.ts.map +1 -0
- package/dist/core/transactionManager.d.ts +11 -0
- package/dist/core/transactionManager.d.ts.map +1 -0
- package/dist/core/validationEngine.d.ts +5 -0
- package/dist/core/validationEngine.d.ts.map +1 -0
- package/dist/hooks/Controller.d.ts +8 -0
- package/dist/hooks/Controller.d.ts.map +1 -0
- package/dist/hooks/useController.d.ts +23 -0
- package/dist/hooks/useController.d.ts.map +1 -0
- package/dist/hooks/useField.d.ts +4 -0
- package/dist/hooks/useField.d.ts.map +1 -0
- package/dist/hooks/useFieldArray.d.ts +23 -0
- package/dist/hooks/useFieldArray.d.ts.map +1 -0
- package/dist/hooks/useForm.d.ts +3 -0
- package/dist/hooks/useForm.d.ts.map +1 -0
- package/dist/hooks/useFormState.d.ts +9 -0
- package/dist/hooks/useFormState.d.ts.map +1 -0
- package/dist/hooks/useWatch.d.ts +22 -0
- package/dist/hooks/useWatch.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +669 -0
- package/dist/index.mjs.map +1 -0
- package/dist/resolvers/yupResolver.d.ts +4 -0
- package/dist/resolvers/yupResolver.d.ts.map +1 -0
- package/dist/resolvers/zodResolver.d.ts +4 -0
- package/dist/resolvers/zodResolver.d.ts.map +1 -0
- package/dist/types/form.d.ts +94 -0
- package/dist/types/form.d.ts.map +1 -0
- package/dist/types/path.d.ts +9 -0
- package/dist/types/path.d.ts.map +1 -0
- package/dist/types/resolver.d.ts +7 -0
- package/dist/types/resolver.d.ts.map +1 -0
- package/dist/utils/clone.d.ts +2 -0
- package/dist/utils/clone.d.ts.map +1 -0
- package/dist/utils/get.d.ts +2 -0
- package/dist/utils/get.d.ts.map +1 -0
- package/dist/utils/getInputValue.d.ts +7 -0
- package/dist/utils/getInputValue.d.ts.map +1 -0
- package/dist/utils/isEqual.d.ts +2 -0
- package/dist/utils/isEqual.d.ts.map +1 -0
- package/dist/utils/path.d.ts +3 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/set.d.ts +2 -0
- package/dist/utils/set.d.ts.map +1 -0
- package/dist/utils/unset.d.ts +2 -0
- package/dist/utils/unset.d.ts.map +1 -0
- package/package.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 blnk contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# blnk
|
|
2
|
+
|
|
3
|
+
`blnk` is an MVP React 19 form library with React Hook Form-like DX and a subscription-based mutable store under the hood. Inputs stay uncontrolled, while fields and selected form state can subscribe to precise updates.
|
|
4
|
+
|
|
5
|
+
## Docs
|
|
6
|
+
|
|
7
|
+
- [Subscriptions](./docs/concepts/subscriptions.md)
|
|
8
|
+
- [Uncontrolled inputs](./docs/concepts/uncontrolled-inputs.md)
|
|
9
|
+
- [Conditional fields](./docs/guides/conditional-fields.md)
|
|
10
|
+
- [Field arrays](./docs/guides/field-arrays.md)
|
|
11
|
+
- [Validation](./docs/guides/validation.md)
|
|
12
|
+
- [Transactions](./docs/guides/transactions.md)
|
|
13
|
+
|
|
14
|
+
## Examples
|
|
15
|
+
|
|
16
|
+
- [LoginFormExample](./src/examples/LoginFormExample.tsx)
|
|
17
|
+
- [WizardFormExample](./src/examples/WizardFormExample.tsx)
|
|
18
|
+
- [ServerPatchTransactionExample](./src/examples/ServerPatchTransactionExample.tsx)
|
|
19
|
+
- [ControlledInputExample](./src/examples/ControlledInputExample.tsx)
|
|
20
|
+
- [CheckboxConditionalFieldExample](./src/examples/CheckboxConditionalFieldExample.tsx)
|
|
21
|
+
- [ComplexFormExample](./src/examples/ComplexFormExample.tsx)
|
|
22
|
+
|
|
23
|
+
## Basic Usage
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { useForm } from "blnk";
|
|
27
|
+
|
|
28
|
+
type FormValues = {
|
|
29
|
+
user: {
|
|
30
|
+
email: string;
|
|
31
|
+
age: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const form = useForm<FormValues>({
|
|
36
|
+
defaultValues: {
|
|
37
|
+
user: { email: "", age: 18 },
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
<form onSubmit={form.handleSubmit((data) => console.log(data.user.email))}>
|
|
42
|
+
<input {...form.register("user.email")} />
|
|
43
|
+
<input type="number" {...form.register("user.age", { valueAsNumber: true })} />
|
|
44
|
+
</form>;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Nested Fields
|
|
48
|
+
|
|
49
|
+
Dot paths support nested objects and arrays:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
form.setValue("user.age", 25);
|
|
53
|
+
form.setValue("addresses.0.city", "Minsk");
|
|
54
|
+
form.watch("user.email");
|
|
55
|
+
form.reset();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`register`, `setValue`, `watch`, and `handleSubmit` are generic-aware through `Path<T>` and `PathValue<T, P>`.
|
|
59
|
+
|
|
60
|
+
## Validation Resolver
|
|
61
|
+
|
|
62
|
+
Use a custom resolver:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const form = useForm<FormValues>({
|
|
66
|
+
resolver: async (values) => ({
|
|
67
|
+
values,
|
|
68
|
+
errors: values.user.email ? {} : { user: { email: { type: "required", message: "Required" } } },
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Zod-like and Yup-like schemas are supported through lightweight adapters:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { zodResolver } from "blnk";
|
|
77
|
+
|
|
78
|
+
const form = useForm<FormValues>({
|
|
79
|
+
schema: userSchema,
|
|
80
|
+
resolver: zodResolver(userSchema),
|
|
81
|
+
mode: "onBlur",
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Async Validation
|
|
86
|
+
|
|
87
|
+
Field-level validators can be sync or async. Async validators support debouncing and stale result protection.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const form = useForm<FormValues>({
|
|
91
|
+
asyncValidatorDebounceMs: 250,
|
|
92
|
+
asyncValidators: {
|
|
93
|
+
"user.email": async (email) => {
|
|
94
|
+
const exists = await api.checkEmail(email);
|
|
95
|
+
return exists ? "Email already exists" : true;
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Field Arrays
|
|
102
|
+
|
|
103
|
+
`useFieldArray` keeps stable `id` values for React keys.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
const skills = useFieldArray({ control: form.control, name: "skills" });
|
|
107
|
+
|
|
108
|
+
skills.append({ title: "", level: "junior" });
|
|
109
|
+
skills.remove(0);
|
|
110
|
+
skills.move(0, 1);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Subscriptions
|
|
114
|
+
|
|
115
|
+
Use `useWatch` when React UI needs to rerender from form values, `useField` for field-level snapshots, and `useFormState` for selector-based form state subscriptions.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const emailValue = useWatch({ control: form.control, name: "user.email" });
|
|
119
|
+
const email = useField(form.control, "user.email");
|
|
120
|
+
|
|
121
|
+
const state = useFormState({
|
|
122
|
+
control: form.control,
|
|
123
|
+
selector: (formState) => ({
|
|
124
|
+
error: formState.errors.user?.email,
|
|
125
|
+
isDirty: formState.isDirty,
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`form.watch("user.email")` is an imperative read. `form.watch("user.email", callback)` is an imperative subscription. `useWatch` is the React hook for conditional UI.
|
|
131
|
+
|
|
132
|
+
## Transactions
|
|
133
|
+
|
|
134
|
+
Transactions batch several mutations into one notification wave.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
form.transaction(() => {
|
|
138
|
+
form.setValue("user.name", "Gleb");
|
|
139
|
+
form.setValue("user.email", "gleb@example.com");
|
|
140
|
+
form.setValue("user.age", 25);
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## MVP Limits
|
|
145
|
+
|
|
146
|
+
- Field array metadata cleanup is still newer than the core field path flow, so keep array-heavy forms covered by tests.
|
|
147
|
+
- Resolver adapters are duck-typed to keep Zod/Yup optional peer dependencies.
|
|
148
|
+
- Package publishing metadata is intentionally minimal.
|
package/README.ru.md
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# blnk
|
|
2
|
+
|
|
3
|
+
## Docs
|
|
4
|
+
|
|
5
|
+
- [Subscriptions](./docs/concepts/subscriptions.md)
|
|
6
|
+
- [Uncontrolled inputs](./docs/concepts/uncontrolled-inputs.md)
|
|
7
|
+
- [Conditional fields](./docs/guides/conditional-fields.md)
|
|
8
|
+
- [Field arrays](./docs/guides/field-arrays.md)
|
|
9
|
+
- [Validation](./docs/guides/validation.md)
|
|
10
|
+
- [Transactions](./docs/guides/transactions.md)
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
- [LoginFormExample](./src/examples/LoginFormExample.tsx)
|
|
15
|
+
- [WizardFormExample](./src/examples/WizardFormExample.tsx)
|
|
16
|
+
- [ServerPatchTransactionExample](./src/examples/ServerPatchTransactionExample.tsx)
|
|
17
|
+
- [ControlledInputExample](./src/examples/ControlledInputExample.tsx)
|
|
18
|
+
- [CheckboxConditionalFieldExample](./src/examples/CheckboxConditionalFieldExample.tsx)
|
|
19
|
+
- [ComplexFormExample](./src/examples/ComplexFormExample.tsx)
|
|
20
|
+
|
|
21
|
+
`blnk` β Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ° ΡΠΎΡΠΌ Π΄Π»Ρ React 19. Π‘Π½Π°ΡΡΠΆΠΈ ΠΎΠ½Π° ΠΏΠΎΡ
ΠΎΠΆΠ° Π½Π° React Hook Form: ΡΡ Π²ΡΠ·ΡΠ²Π°Π΅ΡΡ `useForm`, ΡΠ΅Π³ΠΈΡΡΡΠΈΡΡΠ΅ΡΡ ΠΏΠΎΠ»Ρ ΡΠ΅ΡΠ΅Π· `register` ΠΈ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅ΡΡ ΡΠΎΡΠΌΡ ΡΠ΅ΡΠ΅Π· `handleSubmit`. ΠΠ½ΡΡΡΠΈ ΠΎΠ½Π° ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΈΠ½Π°ΡΠ΅: Π·Π½Π°ΡΠ΅Π½ΠΈΡ Π»Π΅ΠΆΠ°Ρ Π² mutable store, Π° ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°ΡΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π½Π° Π½ΡΠΆΠ½ΡΠ΅ ΠΏΠΎΠ»Ρ.
|
|
22
|
+
|
|
23
|
+
ΠΡΠΎ Π·Π½Π°ΡΠΈΡ, ΡΡΠΎ Π²Π²ΠΎΠ΄ Π² ΠΎΠ΄Π½ΠΎ ΠΏΠΎΠ»Π΅ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΏΠ΅ΡΠ΅ΡΠΈΡΠΎΠ²ΡΠ²Π°ΡΡ Π²ΡΡ ΡΠΎΡΠΌΡ.
|
|
24
|
+
|
|
25
|
+
## Π‘Π°ΠΌΡΠΉ ΠΊΠΎΡΠΎΡΠΊΠΈΠΉ ΠΏΡΠΈΠΌΠ΅Ρ
|
|
26
|
+
|
|
27
|
+
ΠΡΠΎΡ ΠΏΡΠΈΠΌΠ΅Ρ ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ Π³Π»Π°Π²Π½ΡΠΉ ΡΡΠ΅Π½Π°ΡΠΈΠΉ: ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΡΠΌΡ, ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡ input ΠΈ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΡΠΈΠΏΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ ΠΏΡΠΈ submit.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { useForm } from "blnk";
|
|
31
|
+
|
|
32
|
+
type FormValues = {
|
|
33
|
+
user: {
|
|
34
|
+
email: string;
|
|
35
|
+
age: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const form = useForm<FormValues>({
|
|
40
|
+
defaultValues: {
|
|
41
|
+
user: { email: "", age: 18 },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<form onSubmit={form.handleSubmit((data) => console.log(data.user.email))}>
|
|
47
|
+
<input {...form.register("user.email")} />
|
|
48
|
+
<input type="number" {...form.register("user.age", { valueAsNumber: true })} />
|
|
49
|
+
<button type="submit">Save</button>
|
|
50
|
+
</form>
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Π§ΡΠΎ Π·Π΄Π΅ΡΡ Π²Π°ΠΆΠ½ΠΎ:
|
|
55
|
+
|
|
56
|
+
- `FormValues` ΠΎΠΏΠΈΡΡΠ²Π°Π΅Ρ ΡΠΎΡΠΌΡ ΡΠ΅Π»ΠΈΠΊΠΎΠΌ
|
|
57
|
+
- `register("user.email")` Π·Π½Π°Π΅Ρ, ΡΡΠΎ ΡΠ°ΠΊΠΎΠ΅ ΠΏΠΎΠ»Π΅ ΡΡΡΠ΅ΡΡΠ²ΡΠ΅Ρ
|
|
58
|
+
- `data.user.email` ΡΠΈΠΏΠΈΠ·ΠΈΡΠΎΠ²Π°Π½ Π²Π½ΡΡΡΠΈ `handleSubmit`
|
|
59
|
+
- `valueAsNumber` ΠΏΡΠ΅Π²ΡΠ°ΡΠ°Π΅Ρ ΡΡΡΠΎΠΊΡ ΠΈΠ· `<input type="number">` Π² ΡΠΈΡΠ»ΠΎ
|
|
60
|
+
|
|
61
|
+
## ΠΠ°ΠΊ ΡΡΡΡΠΎΠ΅Π½ state
|
|
62
|
+
|
|
63
|
+
`blnk` Π½Π΅ Ρ
ΡΠ°Π½ΠΈΡ ΠΊΠ°ΠΆΠ΄ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ input Π² React state. ΠΠΌΠ΅ΡΡΠΎ ΡΡΠΎΠ³ΠΎ ΡΠΎΡΠΌΠ° Ρ
ΡΠ°Π½ΠΈΡ Π·Π½Π°ΡΠ΅Π½ΠΈΡ Π²ΠΎ Π²Π½ΡΡΡΠ΅Π½Π½Π΅ΠΌ ΠΎΠ±ΡΠ΅ΠΊΡΠ΅.
|
|
64
|
+
|
|
65
|
+
ΠΠΎΠ³Π΄Π° ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΠΏΠ΅ΡΠ°ΡΠ°Π΅Ρ:
|
|
66
|
+
|
|
67
|
+
1. `register` ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ ΡΠΎΠ±ΡΡΠΈΠ΅ ΠΈΠ· input
|
|
68
|
+
2. Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ° Π΄ΠΎΡΡΠ°Π΅Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΈΠ· ΡΠΎΠ±ΡΡΠΈΡ
|
|
69
|
+
3. Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ Π·Π°ΠΏΠΈΡΡΠ²Π°Π΅ΡΡΡ Π²ΠΎ Π²Π½ΡΡΡΠ΅Π½Π½ΠΈΠΉ store
|
|
70
|
+
4. ΠΎΠ±Π½ΠΎΠ²Π»ΡΡΡΡΡ `dirtyFields`, `touchedFields` ΠΈΠ»ΠΈ `errors`
|
|
71
|
+
5. ΡΠ²Π΅Π΄ΠΎΠΌΠ»ΡΡΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠΈΠΊΠΈ ΡΡΠΎΠ³ΠΎ ΠΏΠΎΠ»Ρ
|
|
72
|
+
|
|
73
|
+
Π’Π°ΠΊΠΎΠΉ ΠΏΠΎΠ΄Ρ
ΠΎΠ΄ ΠΏΠΎΠ»Π΅Π·Π΅Π½ Π΄Π»Ρ Π±ΠΎΠ»ΡΡΠΈΡ
ΡΠΎΡΠΌ. ΠΡΠ»ΠΈ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»ΠΎΡΡ `user.email`, ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Ρ `user.age` Π½Π΅ ΠΎΠ±ΡΠ·Π°Π½ ΡΠ΅ΡΠ΅Π½Π΄Π΅ΡΠΈΡΡΡΡ.
|
|
74
|
+
|
|
75
|
+
## ΠΠ»ΠΎΠΆΠ΅Π½Π½ΡΠ΅ ΠΏΠΎΠ»Ρ
|
|
76
|
+
|
|
77
|
+
ΠΠΎΠ»Ρ Π·Π°ΠΏΠΈΡΡΠ²Π°ΡΡΡΡ ΡΡΡΠΎΠΊΠ°ΠΌΠΈ ΡΠ΅ΡΠ΅Π· ΡΠΎΡΠΊΡ.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
form.setValue("user.age", 25);
|
|
81
|
+
form.setValue("addresses.0.city", "Minsk");
|
|
82
|
+
|
|
83
|
+
const email = form.watch("user.email");
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
ΠΡΡΡ `addresses.0.city` ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ: ΠΌΠ°ΡΡΠΈΠ² `addresses`, ΠΏΠ΅ΡΠ²ΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ, ΠΏΠΎΠ»Π΅ `city`.
|
|
87
|
+
|
|
88
|
+
Π’ΠΈΠΏΡ ΡΠΎΠΆΠ΅ ΡΠ°Π±ΠΎΡΠ°ΡΡ ΡΠ΅ΡΠ΅Π· ΡΡΠΈ ΠΏΡΡΠΈ:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
form.setValue("user.age", 25);
|
|
92
|
+
form.setValue("user.email", "gleb@example.com");
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
ΠΡΠ»ΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ Π½Π΅ΠΏΡΠ°Π²ΠΈΠ»ΡΠ½ΡΠΉ ΡΠΈΠΏ Π·Π½Π°ΡΠ΅Π½ΠΈΡ, TypeScript Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΈΡΡ ΠΎΡΠΈΠ±ΠΊΡ.
|
|
96
|
+
|
|
97
|
+
## register
|
|
98
|
+
|
|
99
|
+
`register` Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ props Π΄Π»Ρ input.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<input {...form.register("user.email")} />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
ΠΠ° ΠΏΡΠ°ΠΊΡΠΈΠΊΠ΅ ΡΡΠΎ ΠΎΠ±ΡΠ΅ΠΊΡ:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
{
|
|
109
|
+
name,
|
|
110
|
+
ref,
|
|
111
|
+
onChange,
|
|
112
|
+
onBlur,
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`onChange` ΠΏΠΈΡΠ΅Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ Π² store. `onBlur` ΠΎΡΠΌΠ΅ΡΠ°Π΅Ρ ΠΏΠΎΠ»Π΅ ΠΊΠ°ΠΊ touched ΠΈ ΠΌΠΎΠΆΠ΅Ρ Π·Π°ΠΏΡΡΡΠΈΡΡ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΡ, Π΅ΡΠ»ΠΈ ΡΠΎΡΠΌΠ° Π½Π°ΡΡΡΠΎΠ΅Π½Π° Π½Π° `mode: "onBlur"`.
|
|
117
|
+
|
|
118
|
+
ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°ΡΡΡΡ ΠΎΠ±ΡΡΠ½ΡΠ΅ HTML-ΠΏΠΎΠ»Ρ:
|
|
119
|
+
|
|
120
|
+
- text input
|
|
121
|
+
- number input
|
|
122
|
+
- checkbox
|
|
123
|
+
- radio
|
|
124
|
+
- select
|
|
125
|
+
- textarea
|
|
126
|
+
- file input
|
|
127
|
+
|
|
128
|
+
## handleSubmit
|
|
129
|
+
|
|
130
|
+
`handleSubmit` ΠΎΠ±ΠΎΡΠ°ΡΠΈΠ²Π°Π΅Ρ ΡΠ²ΠΎΠΉ submit handler.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<form
|
|
134
|
+
onSubmit={form.handleSubmit(
|
|
135
|
+
(values) => {
|
|
136
|
+
console.log(values.user.email);
|
|
137
|
+
},
|
|
138
|
+
(errors) => {
|
|
139
|
+
console.log(errors);
|
|
140
|
+
},
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
ΠΡΠΈ submit Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ°:
|
|
146
|
+
|
|
147
|
+
1. Π²ΡΠ·ΡΠ²Π°Π΅Ρ `event.preventDefault()`
|
|
148
|
+
2. ΡΡΠ°Π²ΠΈΡ `formState.isSubmitting`
|
|
149
|
+
3. Π·Π°ΠΏΡΡΠΊΠ°Π΅Ρ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΡ Π²ΡΠ΅ΠΉ ΡΠΎΡΠΌΡ
|
|
150
|
+
4. Π²ΡΠ·ΡΠ²Π°Π΅Ρ `onValid`, Π΅ΡΠ»ΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ Π½Π΅Ρ
|
|
151
|
+
5. Π²ΡΠ·ΡΠ²Π°Π΅Ρ `onInvalid`, Π΅ΡΠ»ΠΈ ΠΎΡΠΈΠ±ΠΊΠΈ Π΅ΡΡΡ
|
|
152
|
+
6. ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ `isSubmitted` ΠΈ `submitCount`
|
|
153
|
+
7. ΡΠ½ΠΈΠΌΠ°Π΅Ρ `isSubmitting`
|
|
154
|
+
|
|
155
|
+
Submit handler ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ async.
|
|
156
|
+
|
|
157
|
+
## watch
|
|
158
|
+
|
|
159
|
+
`watch` ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π΄Π²ΡΠΌΡ ΡΠΏΠΎΡΠΎΠ±Π°ΠΌΠΈ.
|
|
160
|
+
|
|
161
|
+
ΠΠΎΠ»ΡΡΠΈΡΡ ΡΠ΅ΠΊΡΡΠ΅Π΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const email = form.watch("user.email");
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
ΠΠΎΠ΄ΠΏΠΈΡΠ°ΡΡΡΡ Π½Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const unsubscribe = form.watch("user.email", (email) => {
|
|
171
|
+
console.log(email);
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
ΠΠΎ Π²ΡΠΎΡΠΎΠΌ Π²Π°ΡΠΈΠ°Π½ΡΠ΅ ΡΡ ΡΠ°ΠΌ Π²ΡΠ·ΡΠ²Π°Π΅ΡΡ `unsubscribe`, ΠΊΠΎΠ³Π΄Π° ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠ° Π±ΠΎΠ»ΡΡΠ΅ Π½Π΅ Π½ΡΠΆΠ½Π°.
|
|
176
|
+
|
|
177
|
+
## useWatch
|
|
178
|
+
|
|
179
|
+
`useWatch` Π½ΡΠΆΠ΅Π½, ΠΊΠΎΠ³Π΄Π° Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ»Ρ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΏΡΠ°Π²Π»ΡΡΡ React UI.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const wantsDetails = useWatch({
|
|
183
|
+
control: form.control,
|
|
184
|
+
name: "wantsDetails",
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
ΠΡΠΈΠΌΠ΅Ρ: ΡΠ΅ΠΊΠ±ΠΎΠΊΡ ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΠΎΠ΅ ΠΏΠΎΠ»Π΅.
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
{
|
|
192
|
+
wantsDetails ? <input {...form.register("details")} /> : null;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Π Π°Π·Π½ΠΈΡΠ° ΠΏΡΠΎΡΡΠ°Ρ:
|
|
197
|
+
|
|
198
|
+
- `form.watch("field")` ΡΠΈΡΠ°Π΅Ρ ΡΠ΅ΠΊΡΡΠ΅Π΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅
|
|
199
|
+
- `form.watch("field", callback)` ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°Π΅ΡΡΡ Π²ΡΡΡΠ½ΡΡ
|
|
200
|
+
- `useWatch(...)` ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°Π΅Ρ React-ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΈ Π²ΡΠ·ΡΠ²Π°Π΅Ρ ΡΠ΅ΡΠ΅Π½Π΄Π΅Ρ
|
|
201
|
+
|
|
202
|
+
## formState
|
|
203
|
+
|
|
204
|
+
`formState` Ρ
ΡΠ°Π½ΠΈΡ ΡΠ»ΡΠΆΠ΅Π±Π½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΡΠΎΡΠΌΡ.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
form.formState.isDirty;
|
|
208
|
+
form.formState.isSubmitting;
|
|
209
|
+
form.formState.errors.user?.email;
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
ΠΠ»Π°Π²Π½ΡΠ΅ ΠΏΠΎΠ»Ρ:
|
|
213
|
+
|
|
214
|
+
- `errors`: ΠΎΡΠΈΠ±ΠΊΠΈ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠΈ
|
|
215
|
+
- `touchedFields`: ΠΏΠΎΠ»Ρ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΏΠΎΡΠ΅ΡΡΠ»ΠΈ ΡΠΎΠΊΡΡ
|
|
216
|
+
- `dirtyFields`: ΠΏΠΎΠ»Ρ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΎΡΠ»ΠΈΡΠ°ΡΡΡΡ ΠΎΡ `defaultValues`
|
|
217
|
+
- `isDirty`: Π² ΡΠΎΡΠΌΠ΅ Π΅ΡΡΡ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½Π½ΡΠ΅ ΠΏΠΎΠ»Ρ
|
|
218
|
+
- `isValid`: Π² ΡΠΎΡΠΌΠ΅ Π½Π΅Ρ ΠΎΡΠΈΠ±ΠΎΠΊ
|
|
219
|
+
- `isValidating`: ΠΈΠ΄Π΅Ρ async validation
|
|
220
|
+
- `isSubmitting`: ΠΈΠ΄Π΅Ρ submit
|
|
221
|
+
- `isSubmitted`: ΡΠΎΡΠΌΠ° ΡΠΆΠ΅ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ»Π°ΡΡ
|
|
222
|
+
- `submitCount`: ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ submit ΠΏΠΎΠΏΡΡΠΎΠΊ
|
|
223
|
+
|
|
224
|
+
## useField
|
|
225
|
+
|
|
226
|
+
`useField` Π½ΡΠΆΠ΅Π½, ΠΊΠΎΠ³Π΄Π° ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠ»Π΅Π΄ΠΈΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π·Π° ΠΎΠ΄Π½ΠΈΠΌ ΠΏΠΎΠ»Π΅ΠΌ.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
const email = useField(form.control, "user.email");
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
ΠΠ½ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
{
|
|
236
|
+
value,
|
|
237
|
+
error,
|
|
238
|
+
isDirty,
|
|
239
|
+
isTouched,
|
|
240
|
+
isValidating,
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
ΠΡΠ»ΠΈ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡΡΡ Π΄ΡΡΠ³ΠΎΠ΅ ΠΏΠΎΠ»Π΅, ΡΡΠΎΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΎΠ±Π½ΠΎΠ²Π»ΡΡΡΡΡ ΠΈΠ·-Π·Π° Π½Π΅Π³ΠΎ.
|
|
245
|
+
|
|
246
|
+
## useFormState
|
|
247
|
+
|
|
248
|
+
`useFormState` Π½ΡΠΆΠ΅Π½, ΠΊΠΎΠ³Π΄Π° ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ Π½ΡΠΆΠ½Π° Π½Π΅ Π²ΡΡ ΡΠΎΡΠΌΠ°, Π° Π²ΡΠ±ΡΠ°Π½Π½Π°Ρ ΡΠ°ΡΡΡ `formState`.
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
const state = useFormState({
|
|
252
|
+
control: form.control,
|
|
253
|
+
selector: (formState) => ({
|
|
254
|
+
error: formState.errors.user?.email,
|
|
255
|
+
isDirty: formState.isDirty,
|
|
256
|
+
}),
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Selector ΠΏΠΎΠΌΠΎΠ³Π°Π΅Ρ Π½Π΅ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°ΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π½Π° Π»ΠΈΡΠ½ΠΈΠ΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ.
|
|
261
|
+
|
|
262
|
+
## ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΡΠ΅ΡΠ΅Π· resolver
|
|
263
|
+
|
|
264
|
+
Resolver β ΡΡΠΎ ΡΡΠ½ΠΊΡΠΈΡ, ΠΊΠΎΡΠΎΡΠ°Ρ ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ Π²ΡΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΡΠΎΡΠΌΡ ΠΈ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ Π»ΠΈΠ±ΠΎ Π΄Π°Π½Π½ΡΠ΅, Π»ΠΈΠ±ΠΎ ΠΎΡΠΈΠ±ΠΊΠΈ.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
const form = useForm<FormValues>({
|
|
268
|
+
resolver: async (values) => ({
|
|
269
|
+
values,
|
|
270
|
+
errors: values.user.email
|
|
271
|
+
? {}
|
|
272
|
+
: { user: { email: { type: "required", message: "Email is required" } } },
|
|
273
|
+
}),
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Π€ΠΎΡΠΌΠ°Ρ ΠΎΡΠΈΠ±ΠΊΠΈ:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
{
|
|
281
|
+
type: "required",
|
|
282
|
+
message: "Email is required",
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Π’Π°ΠΊΠΎΠΉ ΡΠ»ΠΎΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ°ΡΡ Π»ΡΠ±ΡΡ ΡΡ
Π΅ΠΌΡ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠΈ.
|
|
287
|
+
|
|
288
|
+
## Zod ΠΈ Yup
|
|
289
|
+
|
|
290
|
+
Π Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ΅ Π΅ΡΡΡ Π°Π΄Π°ΠΏΡΠ΅ΡΡ:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import { zodResolver, yupResolver } from "blnk";
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
ΠΡΠΈΠΌΠ΅Ρ Ρ Zod-like ΡΡ
Π΅ΠΌΠΎΠΉ:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
const form = useForm<FormValues>({
|
|
300
|
+
resolver: zodResolver(userSchema),
|
|
301
|
+
mode: "onBlur",
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Zod ΠΈ Yup Π½Π΅ ΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½Ρ ΠΊΠ°ΠΊ ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΡΠ΅ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ. ΠΠ½ΠΈ ΡΠΊΠ°Π·Π°Π½Ρ ΠΊΠ°ΠΊ optional peer dependencies, ΠΏΠΎΡΡΠΎΠΌΡ ΠΏΡΠΎΠ΅ΠΊΡ ΡΠ°ΠΌ ΡΠ΅ΡΠ°Π΅Ρ, Π½ΡΠΆΠ½Ρ ΠΎΠ½ΠΈ Π΅ΠΌΡ ΠΈΠ»ΠΈ Π½Π΅Ρ.
|
|
306
|
+
|
|
307
|
+
## Field-level validation
|
|
308
|
+
|
|
309
|
+
ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠ²Π΅ΡΠΈΡΡ Π½Π° ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎΠ΅ ΠΏΠΎΠ»Π΅.
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
<input
|
|
313
|
+
{...form.register("user.email", {
|
|
314
|
+
required: "Email is required",
|
|
315
|
+
validate: (email) => email.includes("@") || "Email is invalid",
|
|
316
|
+
})}
|
|
317
|
+
/>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`validate` Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ:
|
|
321
|
+
|
|
322
|
+
- `true`, Π΅ΡΠ»ΠΈ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ Π²Π°Π»ΠΈΠ΄Π½ΠΎ
|
|
323
|
+
- ΡΡΡΠΎΠΊΡ, Π΅ΡΠ»ΠΈ Π½ΡΠΆΠ½ΠΎ ΠΏΠΎΠΊΠ°Π·Π°ΡΡ ΠΎΡΠΈΠ±ΠΊΡ
|
|
324
|
+
- ΠΎΠ±ΡΠ΅ΠΊΡ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ Π½ΡΠΆΠ΅Π½ ΡΠ²ΠΎΠΉ `type`
|
|
325
|
+
|
|
326
|
+
## Async validation
|
|
327
|
+
|
|
328
|
+
ΠΡΠΈΠ½Ρ
ΡΠΎΠ½Π½Π°Ρ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΡ ΠΏΠΎΠ»Π΅Π·Π½Π° Π΄Π»Ρ ΠΏΡΠΎΠ²Π΅ΡΠΎΠΊ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠ΅. ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ, Π·Π°Π½ΡΡ Π»ΠΈ email.
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const form = useForm<FormValues>({
|
|
332
|
+
mode: "onBlur",
|
|
333
|
+
asyncValidatorDebounceMs: 250,
|
|
334
|
+
asyncValidators: {
|
|
335
|
+
"user.email": async (email) => {
|
|
336
|
+
const exists = await api.checkEmail(email);
|
|
337
|
+
return exists ? "Email already exists" : true;
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
ΠΠ°ΠΆΠ½ΡΠ΅ Π΄Π΅ΡΠ°Π»ΠΈ:
|
|
344
|
+
|
|
345
|
+
- debounce Π·Π°Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ Π·Π°ΠΏΡΠΎΡ, Π΅ΡΠ»ΠΈ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΠΏΡΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅Ρ Π²Π²ΠΎΠ΄
|
|
346
|
+
- stale result protection ΠΈΠ³Π½ΠΎΡΠΈΡΡΠ΅Ρ ΡΡΠ°ΡΡΠΉ ΠΎΡΠ²Π΅Ρ, Π΅ΡΠ»ΠΈ ΡΠΆΠ΅ Π½Π°ΡΠ°Π»Π°ΡΡ Π½ΠΎΠ²Π°Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠ°
|
|
347
|
+
- `formState.isValidating` ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ, ΡΡΠΎ ΠΈΠ΄Π΅Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠ°
|
|
348
|
+
- `formState.validatingFields` ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ, ΠΊΠ°ΠΊΠΈΠ΅ ΠΏΠΎΠ»Ρ ΠΏΡΠΎΠ²Π΅ΡΡΡΡΡΡ
|
|
349
|
+
|
|
350
|
+
## Field arrays
|
|
351
|
+
|
|
352
|
+
`useFieldArray` ΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ ΠΌΠ°ΡΡΠΈΠ²Π°ΠΌΠΈ ΠΏΠΎΠ»Π΅ΠΉ.
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
const skills = useFieldArray({
|
|
356
|
+
control: form.control,
|
|
357
|
+
name: "skills",
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
skills.append({ title: "", level: "junior" });
|
|
361
|
+
skills.remove(0);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`fields` ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ ΠΌΠ°ΡΡΠΈΠ²Π° ΠΏΠ»ΡΡ ΡΡΠ°Π±ΠΈΠ»ΡΠ½ΡΠΉ `id`.
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
{
|
|
368
|
+
skills.fields.map((skill, index) => (
|
|
369
|
+
<div key={skill.id}>
|
|
370
|
+
<input {...form.register(`skills.${index}.title`)} />
|
|
371
|
+
</div>
|
|
372
|
+
));
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉ `skill.id` ΠΊΠ°ΠΊ React key. ΠΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉ `index` ΠΊΠ°ΠΊ key, ΠΏΠΎΡΠΎΠΌΡ ΡΡΠΎ ΠΏΡΠΈ ΡΠ΄Π°Π»Π΅Π½ΠΈΠΈ ΠΈΠ»ΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅ΡΠ΅Π½ΠΈΠΈ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² React ΠΌΠΎΠΆΠ΅Ρ ΠΏΠ΅ΡΠ΅ΠΏΡΡΠ°ΡΡ DOM.
|
|
377
|
+
|
|
378
|
+
ΠΠΎΡΡΡΠΏΠ½ΡΠ΅ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ:
|
|
379
|
+
|
|
380
|
+
- `append`: Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π² ΠΊΠΎΠ½Π΅Ρ
|
|
381
|
+
- `remove`: ΡΠ΄Π°Π»ΠΈΡΡ ΠΏΠΎ ΠΈΠ½Π΄Π΅ΠΊΡΡ
|
|
382
|
+
- `insert`: Π²ΡΡΠ°Π²ΠΈΡΡ ΠΏΠΎ ΠΈΠ½Π΄Π΅ΠΊΡΡ
|
|
383
|
+
- `move`: ΠΏΠ΅ΡΠ΅ΠΌΠ΅ΡΡΠΈΡΡ
|
|
384
|
+
- `swap`: ΠΏΠΎΠΌΠ΅Π½ΡΡΡ ΠΌΠ΅ΡΡΠ°ΠΌΠΈ
|
|
385
|
+
- `replace`: Π·Π°ΠΌΠ΅Π½ΠΈΡΡ Π²Π΅ΡΡ ΠΌΠ°ΡΡΠΈΠ²
|
|
386
|
+
|
|
387
|
+
## Transactions API
|
|
388
|
+
|
|
389
|
+
Transaction Π½ΡΠΆΠ½Π°, ΠΊΠΎΠ³Π΄Π° ΡΡ ΠΌΠ΅Π½ΡΠ΅ΡΡ ΡΡΠ°Π·Ρ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΠΏΠΎΠ»Π΅ΠΉ.
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
form.transaction(() => {
|
|
393
|
+
form.setValue("user.name", "Gleb");
|
|
394
|
+
form.setValue("user.email", "gleb@example.com");
|
|
395
|
+
form.setValue("user.age", 25);
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
ΠΠ΅Π· transaction ΠΊΠ°ΠΆΠ΄ΠΎΠ΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΡΠ²Π΅Π΄ΠΎΠΌΠ»ΡΠ΅Ρ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠΈΠΊΠΎΠ² ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎ. ΠΠ½ΡΡΡΠΈ transaction ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ ΠΏΡΠΈΠΌΠ΅Π½ΡΡΡΡΡ ΡΡΠ°Π·Ρ, Π½ΠΎ ΡΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΡ ΠΎΡΠΏΡΠ°Π²Π»ΡΡΡΡΡ ΠΎΠ΄Π½ΠΎΠΉ ΠΏΠ°ΡΠΊΠΎΠΉ ΠΏΠΎΡΠ»Π΅ `commitTransaction`.
|
|
400
|
+
|
|
401
|
+
ΠΡΠΎ ΡΠ΄ΠΎΠ±Π½ΠΎ Π΄Π»Ρ:
|
|
402
|
+
|
|
403
|
+
- server patches
|
|
404
|
+
- wizard forms
|
|
405
|
+
- autosave
|
|
406
|
+
- Π·Π°ΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ ΡΠΎΡΠΌΡ ΠΈΠ· ΡΠ°Π±Π»ΠΎΠ½Π°
|
|
407
|
+
- ΠΌΠ°ΡΡΠΎΠ²ΠΎΠ³ΠΎ reset ΡΠ°ΡΡΠΈ ΡΠΎΡΠΌΡ
|
|
408
|
+
|
|
409
|
+
ΠΠΈΠ·ΠΊΠΎΡΡΠΎΠ²Π½Π΅Π²ΡΠΉ API ΡΠΎΠΆΠ΅ Π΄ΠΎΡΡΡΠΏΠ΅Π½:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
form.beginTransaction();
|
|
413
|
+
form.setValue("user.name", "Gleb");
|
|
414
|
+
form.setValue("user.age", 25);
|
|
415
|
+
form.commitTransaction();
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Π Π±ΠΎΠ»ΡΡΠΈΠ½ΡΡΠ²Π΅ ΡΠ»ΡΡΠ°Π΅Π² ΡΠ΄ΠΎΠ±Π½Π΅Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ `form.transaction`.
|
|
419
|
+
|
|
420
|
+
## reset
|
|
421
|
+
|
|
422
|
+
`reset` Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠΎΡΠΌΡ ΠΊ `defaultValues`.
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
form.reset();
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
ΠΠΎΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ Π½ΠΎΠ²ΡΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡ:
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
form.reset({
|
|
432
|
+
user: {
|
|
433
|
+
email: "new@example.com",
|
|
434
|
+
age: 30,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
ΠΠΎΡΠ»Π΅ reset ΠΎΡΠΈΡΠ°ΡΡΡΡ ΠΎΡΠΈΠ±ΠΊΠΈ, touched, dirty ΠΈ submit state.
|
|
440
|
+
|
|
441
|
+
## Π§ΡΠΎ ΡΠΆΠ΅ Π΅ΡΡΡ
|
|
442
|
+
|
|
443
|
+
- RHF-like API
|
|
444
|
+
- uncontrolled inputs
|
|
445
|
+
- mutable store
|
|
446
|
+
- ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ Π½Π° ΠΏΠΎΠ»Ρ
|
|
447
|
+
- selector-based ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ Π½Π° `formState`
|
|
448
|
+
- `useWatch` Π΄Π»Ρ ΡΡΠ»ΠΎΠ²Π½ΠΎΠ³ΠΎ React UI
|
|
449
|
+
- nested paths
|
|
450
|
+
- arrays ΠΈ `useFieldArray`
|
|
451
|
+
- custom resolver
|
|
452
|
+
- Zod/Yup adapters
|
|
453
|
+
- field-level validation
|
|
454
|
+
- async validation Ρ debounce
|
|
455
|
+
- Π·Π°ΡΠΈΡΠ° ΠΎΡ race condition
|
|
456
|
+
- transactions
|
|
457
|
+
- TypeScript-ΡΠΈΠΏΡ Π΄Π»Ρ ΠΎΡΠ½ΠΎΠ²Π½ΡΡ
ΡΡΠ΅Π½Π°ΡΠΈΠ΅Π²
|
|
458
|
+
|
|
459
|
+
## ΠΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΡ MVP
|
|
460
|
+
|
|
461
|
+
- ΠΠ΅Ρ adapter Π΄Π»Ρ controlled components
|
|
462
|
+
- React rendering tests ΠΏΠΎΠΊΠ° ΠΏΠΎΠΊΡΡΠ²Π°ΡΡ `useWatch` ΠΈ checkbox conditional-field example
|
|
463
|
+
- ΠΡΠΈΡΡΠΊΠ° `errors`, `touched` ΠΈ `dirty` ΠΏΡΠΈ ΡΠ»ΠΎΠΆΠ½ΡΡ
ΠΏΠ΅ΡΠ΅ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°Ρ
field arrays ΠΏΠΎΠΊΠ° Π±Π°Π·ΠΎΠ²Π°Ρ
|
|
464
|
+
- Zod/Yup adapters ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡ duck typing
|
|
465
|
+
- Package publishing metadata ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡΠ½Π°Ρ
|
|
466
|
+
- `Path<T>` ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ ΡΠ°Π·ΡΠΌΠ½ΠΎΠΉ Π³Π»ΡΠ±ΠΈΠ½ΠΎΠΉ, ΡΡΠΎΠ±Ρ TypeScript Π½Π΅ ΡΡ
ΠΎΠ΄ΠΈΠ» Π² Π±Π΅ΡΠΊΠΎΠ½Π΅ΡΠ½ΡΡ ΡΠ΅ΠΊΡΡΡΠΈΡ
|
|
467
|
+
|
|
468
|
+
## ΠΠ°ΠΊ ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ ΠΏΡΠΎΠ΅ΠΊΡ
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
npm test
|
|
472
|
+
npm run typecheck
|
|
473
|
+
npm run build
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
`lint`-ΡΠΊΡΠΈΠΏΡΠ° ΡΠ΅ΠΉΡΠ°Ρ Π½Π΅Ρ.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createFormControl.d.ts","sourceRoot":"","sources":["../../src/core/createFormControl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EACX,WAAW,EAIX,cAAc,EAGf,MAAM,eAAe,CAAC;AA8DvB,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW,EACrD,OAAO,GAAE,cAAc,CAAC,CAAC,CAAM,GAC9B,WAAW,CAAC,CAAC,CAAC,CAwYhB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare function getFieldArrayIds(control: object, name: string, length: number): string[];
|
|
2
|
+
export declare function setFieldArrayIds(control: object, name: string, ids: string[]): void;
|
|
3
|
+
export declare function createFieldArrayId(): string;
|
|
4
|
+
export type FieldArrayMetadataOperation = {
|
|
5
|
+
type: "append";
|
|
6
|
+
} | {
|
|
7
|
+
type: "remove";
|
|
8
|
+
index: number;
|
|
9
|
+
} | {
|
|
10
|
+
type: "insert";
|
|
11
|
+
index: number;
|
|
12
|
+
} | {
|
|
13
|
+
type: "move";
|
|
14
|
+
from: number;
|
|
15
|
+
to: number;
|
|
16
|
+
} | {
|
|
17
|
+
type: "swap";
|
|
18
|
+
left: number;
|
|
19
|
+
right: number;
|
|
20
|
+
} | {
|
|
21
|
+
type: "replace";
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=fieldArrayState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fieldArrayState.d.ts","sourceRoot":"","sources":["../../src/core/fieldArrayState.ts"],"names":[],"mappings":"AAiBA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAWxF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAEnF;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,MAAM,MAAM,2BAA2B,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FieldValues, RegisterOptions } from "../types/form";
|
|
2
|
+
import type { Path } from "../types/path";
|
|
3
|
+
export type RegisteredField<T extends FieldValues> = {
|
|
4
|
+
ref: unknown;
|
|
5
|
+
options?: RegisterOptions<T, any>;
|
|
6
|
+
};
|
|
7
|
+
export declare function setRegisteredField<T extends FieldValues>(fields: Map<string, RegisteredField<T>>, refs: Map<string, unknown>, name: Path<T>, ref: unknown, options?: RegisterOptions<T, any>): void;
|
|
8
|
+
export declare function unsetRegisteredField<T extends FieldValues>(fields: Map<string, RegisteredField<T>>, refs: Map<string, unknown>, name: Path<T>): void;
|
|
9
|
+
//# sourceMappingURL=fieldRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fieldRegistry.d.ts","sourceRoot":"","sources":["../../src/core/fieldRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,WAAW,IAAI;IACnD,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;CACnC,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,WAAW,EACtD,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,EACvC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EACb,GAAG,EAAE,OAAO,EACZ,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,GAChC,IAAI,CAIN;AAED,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,WAAW,EACxD,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,EACvC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,GACZ,IAAI,CAIN"}
|