react-form-manage 1.0.8-beta.2 → 1.0.8-beta.21
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/CHANGELOG.md +85 -5
- package/README.md +8 -4
- package/dist/components/Form/FormCleanUp.js +3 -3
- package/dist/components/Form/FormItem.d.ts +4 -2
- package/dist/components/Form/FormItem.js +7 -5
- package/dist/components/Form/FormList.d.ts +2 -2
- package/dist/components/Form/FormList.js +2 -2
- package/dist/hooks/useFormItemControl.d.ts +1 -0
- package/dist/hooks/useFormItemControl.js +60 -23
- package/dist/hooks/useFormListControl.d.ts +2 -1
- package/dist/hooks/useFormListControl.js +60 -10
- package/dist/index.cjs.d.ts +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.esm.d.ts +1 -0
- package/dist/index.js +4 -2
- package/dist/providers/Form.d.ts +4 -2
- package/dist/providers/Form.js +98 -28
- package/dist/stores/formStore.d.ts +27 -2
- package/dist/stores/formStore.js +25 -9
- package/dist/test/TestDialog.d.ts +3 -0
- package/dist/test/TestDialog.js +21 -0
- package/dist/test/TestListener.d.ts +3 -0
- package/dist/test/TestListener.js +17 -0
- package/dist/test/TestSelect.d.ts +6 -0
- package/dist/test/TestSelect.js +24 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/public.d.ts +6 -1
- package/dist/utils/obj.util.d.ts +1 -1
- package/dist/utils/obj.util.js +16 -5
- package/package.json +3 -1
- package/src/App.tsx +98 -4
- package/src/components/Form/FormCleanUp.tsx +3 -7
- package/src/components/Form/FormItem.tsx +56 -31
- package/src/components/Form/FormList.tsx +17 -4
- package/src/components/Form/InputWrapper.tsx +5 -0
- package/src/hooks/useFormItemControl.ts +70 -29
- package/src/hooks/useFormListControl.ts +104 -26
- package/src/index.ts +27 -13
- package/src/providers/Form.tsx +126 -17
- package/src/stores/formStore.ts +319 -288
- package/src/test/TestDialog.tsx +52 -0
- package/src/test/TestListener.tsx +21 -0
- package/src/test/TestSelect.tsx +38 -0
- package/src/types/index.ts +1 -1
- package/src/types/public.ts +7 -1
- package/src/utils/obj.util.ts +44 -13
package/src/index.ts
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
|
+
import { SUBMIT_STATE } from "./constants/form";
|
|
1
2
|
import Form, {
|
|
2
3
|
useForm,
|
|
3
|
-
useWatch,
|
|
4
|
-
useSubmitDataWatch,
|
|
5
4
|
useFormStateWatch,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
useSubmitDataWatch,
|
|
6
|
+
useWatch,
|
|
8
7
|
type FormFieldError,
|
|
8
|
+
type FormProps,
|
|
9
9
|
type SubmitState,
|
|
10
|
+
type UseFormItemStateWatchReturn,
|
|
11
|
+
type ValidationRule,
|
|
10
12
|
} from "./providers/Form";
|
|
11
|
-
import { SUBMIT_STATE } from "./constants/form";
|
|
12
13
|
|
|
13
14
|
import FormItem, { type FormItemProps } from "./components/Form/FormItem";
|
|
14
15
|
import FormList, { type FormListProps } from "./components/Form/FormList";
|
|
16
|
+
import InputWrapper, {
|
|
17
|
+
type InputWrapperProps,
|
|
18
|
+
} from "./components/Form/InputWrapper";
|
|
15
19
|
import Input from "./components/Input";
|
|
16
|
-
import InputWrapper, { type InputWrapperProps } from "./components/Form/InputWrapper";
|
|
17
20
|
|
|
18
21
|
import useFormItemControl from "./hooks/useFormItemControl";
|
|
19
22
|
import useFormListControl from "./hooks/useFormListControl";
|
|
23
|
+
import {
|
|
24
|
+
useFormStore,
|
|
25
|
+
type CleanUpItem,
|
|
26
|
+
type FormInstance,
|
|
27
|
+
type ListenerItem,
|
|
28
|
+
} from "./stores/formStore";
|
|
20
29
|
|
|
21
30
|
export {
|
|
22
31
|
Form,
|
|
@@ -24,20 +33,25 @@ export {
|
|
|
24
33
|
FormList,
|
|
25
34
|
Input,
|
|
26
35
|
InputWrapper,
|
|
36
|
+
SUBMIT_STATE,
|
|
37
|
+
useForm,
|
|
27
38
|
useFormItemControl,
|
|
28
39
|
useFormListControl,
|
|
29
|
-
useForm,
|
|
30
|
-
useWatch,
|
|
31
|
-
useSubmitDataWatch,
|
|
32
40
|
useFormStateWatch,
|
|
33
|
-
|
|
41
|
+
useFormStore,
|
|
42
|
+
useSubmitDataWatch,
|
|
43
|
+
useWatch,
|
|
44
|
+
type CleanUpItem,
|
|
45
|
+
type FormFieldError,
|
|
46
|
+
type FormInstance,
|
|
34
47
|
type FormItemProps,
|
|
35
48
|
type FormListProps,
|
|
49
|
+
type FormProps,
|
|
36
50
|
type InputWrapperProps,
|
|
37
|
-
type
|
|
38
|
-
type FormFieldError,
|
|
51
|
+
type ListenerItem,
|
|
39
52
|
type SubmitState,
|
|
40
|
-
|
|
53
|
+
type UseFormItemStateWatchReturn,
|
|
54
|
+
type ValidationRule,
|
|
41
55
|
};
|
|
42
56
|
|
|
43
57
|
export default Form;
|
package/src/providers/Form.tsx
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
cloneDeep,
|
|
3
|
+
get,
|
|
4
|
+
isEqual,
|
|
5
|
+
isNil,
|
|
6
|
+
isPlainObject,
|
|
7
|
+
last,
|
|
8
|
+
set,
|
|
9
|
+
uniqBy,
|
|
10
|
+
} from "lodash";
|
|
2
11
|
import { useTask } from "minh-custom-hooks-release";
|
|
3
12
|
import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
|
|
4
13
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
5
14
|
import { flushSync } from "react-dom";
|
|
6
15
|
import { useShallow } from "zustand/react/shallow"; // Import useShallow
|
|
7
16
|
import FormCleanUp from "../components/Form/FormCleanUp";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
17
|
+
import { SUBMIT_STATE } from "../constants/form";
|
|
18
|
+
import { ListenerItem, useFormStore } from "../stores/formStore";
|
|
19
|
+
import type {
|
|
20
|
+
PublicFormInstance,
|
|
21
|
+
UseFormItemStateWatchReturn,
|
|
22
|
+
} from "../types/public";
|
|
10
23
|
import { getAllNoneObjStringPath } from "../utils/obj.util";
|
|
11
|
-
export type {
|
|
24
|
+
export type {
|
|
25
|
+
FormFieldError,
|
|
26
|
+
SubmitState,
|
|
27
|
+
UseFormItemStateWatchReturn,
|
|
28
|
+
ValidationRule,
|
|
29
|
+
} from "../types/public";
|
|
12
30
|
|
|
13
31
|
export const FormContext = createContext(null);
|
|
14
32
|
|
|
@@ -40,7 +58,7 @@ export default function Form<T = any>({
|
|
|
40
58
|
...props
|
|
41
59
|
}: FormProps<T>) {
|
|
42
60
|
const {
|
|
43
|
-
appInitValue,
|
|
61
|
+
// appInitValue,
|
|
44
62
|
getFormItemValue,
|
|
45
63
|
setInitData,
|
|
46
64
|
setData,
|
|
@@ -54,7 +72,7 @@ export default function Form<T = any>({
|
|
|
54
72
|
clearFormState,
|
|
55
73
|
} = useFormStore(
|
|
56
74
|
useShallow((state) => ({
|
|
57
|
-
appInitValue: state.initialValues,
|
|
75
|
+
// appInitValue: state.initialValues,
|
|
58
76
|
setInitData: state.setInitData,
|
|
59
77
|
getFormValues: state.getFormValues,
|
|
60
78
|
setFormState: state.setFormState,
|
|
@@ -68,11 +86,11 @@ export default function Form<T = any>({
|
|
|
68
86
|
clearFormState: state.clearFormState,
|
|
69
87
|
|
|
70
88
|
// Test, nhớ xóa sau khi xong
|
|
71
|
-
formStates: state.formStates?.[formName],
|
|
89
|
+
// formStates: state.formStates?.[formName],
|
|
72
90
|
})),
|
|
73
91
|
);
|
|
74
92
|
|
|
75
|
-
const { getListeners } =
|
|
93
|
+
const { getListeners } = useFormStore(
|
|
76
94
|
useShallow((state) => {
|
|
77
95
|
return {
|
|
78
96
|
getListeners: state.getListeners,
|
|
@@ -81,13 +99,51 @@ export default function Form<T = any>({
|
|
|
81
99
|
);
|
|
82
100
|
|
|
83
101
|
const setFieldValue = (name, value, options) => {
|
|
84
|
-
const listener = getListeners().find(
|
|
102
|
+
const listener: ListenerItem | null = getListeners().find(
|
|
85
103
|
(l) => l.name === name && l.formName === formName,
|
|
86
104
|
);
|
|
87
105
|
if (listener) {
|
|
88
|
-
listener
|
|
106
|
+
// Nếu loại listener là array thì gọi onArrayChange
|
|
107
|
+
if (listener.type === "array") {
|
|
108
|
+
// Do nothing if the value is the same for array item to prevent unnecessary re-renders
|
|
109
|
+
if (!isEqual(getFormItemValue(formName, name), value)) {
|
|
110
|
+
listener.onArrayChange(value, options);
|
|
111
|
+
|
|
112
|
+
// Kiểm tra từng path con của array item xem có listener nào không, nếu có thì gọi onChange của listener đó
|
|
113
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
114
|
+
allStringPath.forEach((p) => {
|
|
115
|
+
const findListener = getListeners().find(
|
|
116
|
+
(l) => l.name === `${name}.${p}` && l.formName === formName,
|
|
117
|
+
);
|
|
118
|
+
if (findListener) {
|
|
119
|
+
findListener.onChange(get(value, p), options);
|
|
120
|
+
} else {
|
|
121
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
listener.onChange(value, options);
|
|
127
|
+
}
|
|
89
128
|
} else {
|
|
90
|
-
|
|
129
|
+
// set data for non-listener field
|
|
130
|
+
if (isPlainObject(value) || Array.isArray(value)) {
|
|
131
|
+
// Nếu là object hoặc array thì set từng path con
|
|
132
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
133
|
+
|
|
134
|
+
allStringPath.forEach((p) => {
|
|
135
|
+
const findListener = getListeners().find(
|
|
136
|
+
(l) => l.name === `${name}.${p}` && l.formName === formName,
|
|
137
|
+
);
|
|
138
|
+
if (findListener) {
|
|
139
|
+
findListener.onChange(get(value, p), options);
|
|
140
|
+
} else {
|
|
141
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
setData(formName, name, value);
|
|
146
|
+
}
|
|
91
147
|
}
|
|
92
148
|
};
|
|
93
149
|
|
|
@@ -99,7 +155,31 @@ export default function Form<T = any>({
|
|
|
99
155
|
(l) => l.name === p && l.formName === formName,
|
|
100
156
|
);
|
|
101
157
|
if (listener) {
|
|
102
|
-
listener.
|
|
158
|
+
if (listener.type === "array") {
|
|
159
|
+
// Do nothing if the value is the same for array item to prevent unnecessary re-renders
|
|
160
|
+
if (!isEqual(getFormItemValue(formName, p), get(values, p))) {
|
|
161
|
+
listener.onArrayChange(get(values, listener.name), options);
|
|
162
|
+
|
|
163
|
+
// Kiểm tra từng path con của array item xem có listener nào không, nếu có thì gọi onChange của listener đó
|
|
164
|
+
const nestedAllStringPath: string[] = getAllNoneObjStringPath(
|
|
165
|
+
get(values, p),
|
|
166
|
+
);
|
|
167
|
+
nestedAllStringPath.forEach((np) => {
|
|
168
|
+
{
|
|
169
|
+
const findListener = getListeners().find(
|
|
170
|
+
(l) => l.name === `${p}.${np}` && l.formName === formName,
|
|
171
|
+
);
|
|
172
|
+
if (findListener) {
|
|
173
|
+
findListener.onChange(get(values, `${p}.${np}`), options);
|
|
174
|
+
} else {
|
|
175
|
+
setData(formName, `${p}.${np}`, get(values, `${p}.${np}`));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
listener.onChange(get(values, listener.name), options);
|
|
182
|
+
}
|
|
103
183
|
} else {
|
|
104
184
|
setData(formName, p, get(values, p));
|
|
105
185
|
}
|
|
@@ -165,8 +245,8 @@ export default function Form<T = any>({
|
|
|
165
245
|
|
|
166
246
|
const resultValues = cloneDeep(formValues);
|
|
167
247
|
const cleanValues = {} as T;
|
|
168
|
-
|
|
169
|
-
set(cleanValues, l.name, get(resultValues, l.name));
|
|
248
|
+
uniqBy(listeners, (l: any) => l.name).forEach((l) => {
|
|
249
|
+
set(cleanValues as any, l.name, get(resultValues, l.name));
|
|
170
250
|
});
|
|
171
251
|
|
|
172
252
|
const handleIsolateCase = async () => {
|
|
@@ -271,6 +351,7 @@ export default function Form<T = any>({
|
|
|
271
351
|
setFormState({
|
|
272
352
|
formName,
|
|
273
353
|
isInitied: false,
|
|
354
|
+
submitState: SUBMIT_STATE.IDLE,
|
|
274
355
|
}),
|
|
275
356
|
);
|
|
276
357
|
const totalListenerFields = getListeners();
|
|
@@ -403,9 +484,11 @@ export function useFormContext() {
|
|
|
403
484
|
export function useForm<T = any>(
|
|
404
485
|
formNameOrFormInstance?: string | PublicFormInstance<T>,
|
|
405
486
|
) {
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
487
|
+
const formContext = useContext(FormContext);
|
|
488
|
+
const targetFormName = isNil(formNameOrFormInstance)
|
|
489
|
+
? formContext?.formName
|
|
490
|
+
: typeof formNameOrFormInstance === "object" &&
|
|
491
|
+
formNameOrFormInstance !== null
|
|
409
492
|
? (formNameOrFormInstance as PublicFormInstance<T>).formName
|
|
410
493
|
: (formNameOrFormInstance as string | undefined);
|
|
411
494
|
|
|
@@ -472,7 +555,33 @@ export const useFormStateWatch = <T = any,>(
|
|
|
472
555
|
return formState as any;
|
|
473
556
|
};
|
|
474
557
|
|
|
558
|
+
// Get Form Item State Using name (make sure no same name listener before using this) or formItemId, formNameOrFormInstance
|
|
559
|
+
// reutrn formItem state like isTouched, isDirty, errors
|
|
560
|
+
export const useFormItemStateWatch = <T = any,>(
|
|
561
|
+
nameOrFormItemId: string,
|
|
562
|
+
formNameOrFormInstance?: string | PublicFormInstance<T>,
|
|
563
|
+
): UseFormItemStateWatchReturn => {
|
|
564
|
+
const [formInstance] = useForm<T>(formNameOrFormInstance as any);
|
|
565
|
+
|
|
566
|
+
const listener = useFormStore((state) => {
|
|
567
|
+
return state
|
|
568
|
+
.getListeners()
|
|
569
|
+
.find(
|
|
570
|
+
(l) =>
|
|
571
|
+
l.formName === formInstance?.formName &&
|
|
572
|
+
(l.name === nameOrFormItemId || l.formItemId === nameOrFormItemId),
|
|
573
|
+
);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
return {
|
|
577
|
+
isTouched: listener?.isTouched,
|
|
578
|
+
isDirty: listener?.isDirty,
|
|
579
|
+
errors: listener?.internalErrors || [],
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
|
|
475
583
|
Form.useForm = useForm;
|
|
476
584
|
Form.useWatch = useWatch;
|
|
477
585
|
Form.useSubmitDataWatch = useSubmitDataWatch;
|
|
478
586
|
Form.useFormStateWatch = useFormStateWatch;
|
|
587
|
+
Form.useFormItemStateWatch = useFormItemStateWatch;
|