react-form-manage 1.0.8-beta.16 → 1.0.8-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Form/FormList.d.ts +1 -1
- package/dist/hooks/useFormItemControl.js +7 -4
- package/dist/hooks/useFormListControl.d.ts +1 -0
- package/dist/hooks/useFormListControl.js +53 -3
- package/dist/providers/Form.js +61 -4
- package/dist/stores/formStore.d.ts +3 -0
- package/dist/stores/formStore.js +7 -2
- package/package.json +1 -1
- package/src/App.tsx +23 -3
- package/src/components/Form/FormList.tsx +1 -1
- package/src/hooks/useFormItemControl.ts +8 -4
- package/src/hooks/useFormListControl.ts +83 -14
- package/src/providers/Form.tsx +81 -6
- package/src/stores/formStore.ts +12 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { get, isNil } from "lodash";
|
|
1
|
+
import { get, has, isNil } from "lodash";
|
|
2
2
|
import { useTaskEffect } from "minh-custom-hooks-release";
|
|
3
3
|
import { useEffect, useMemo } from "react";
|
|
4
4
|
import { useShallow } from "zustand/react/shallow";
|
|
@@ -319,10 +319,13 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
|
|
|
319
319
|
const cacheData = getCacheData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName));
|
|
320
320
|
if (cacheData) {
|
|
321
321
|
const getNewDataFromCache = get(cacheData, name);
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
const isIncludeDirectoryInCache = has(cacheData, name);
|
|
323
|
+
if (!isIncludeDirectoryInCache && isNil(getNewDataFromCache)) {
|
|
324
|
+
onChange(initialValue, {
|
|
325
|
+
notTriggerDirty: true
|
|
326
|
+
});
|
|
324
327
|
} else
|
|
325
|
-
onChange(getNewDataFromCache);
|
|
328
|
+
onChange(getNewDataFromCache, { notTriggerDirty: true });
|
|
326
329
|
}
|
|
327
330
|
}, [name, formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)]);
|
|
328
331
|
useEffect(() => {
|
|
@@ -5,13 +5,18 @@ import { useShallow } from "zustand/react/shallow";
|
|
|
5
5
|
import { useFormContext } from "../providers/Form";
|
|
6
6
|
import { useFormStore } from "../stores/formStore";
|
|
7
7
|
function useFormListControl({ name, form, initialValues, formName }) {
|
|
8
|
+
const [formItemId] = useState(v4());
|
|
8
9
|
const contextForm = useFormContext();
|
|
9
10
|
const getFormValues = useFormStore((state) => state.getFormValues);
|
|
10
11
|
const [listFormInitValues, setListFormInitValues] = useState(void 0);
|
|
11
|
-
const { clearCacheData, setCacheData } = useFormStore(useShallow((state) => ({
|
|
12
|
+
const { clearCacheData, setCacheData, setListener, getListener } = useFormStore(useShallow((state) => ({
|
|
13
|
+
// Cache
|
|
12
14
|
cacheData: state.cacheData,
|
|
13
15
|
clearCacheData: state.clearCacheData,
|
|
14
|
-
setCacheData: state.setCacheData
|
|
16
|
+
setCacheData: state.setCacheData,
|
|
17
|
+
// Listener
|
|
18
|
+
setListener: state.setListener,
|
|
19
|
+
getListener: state.getListener
|
|
15
20
|
})));
|
|
16
21
|
const { setCleanUpStack } = useFormStore(useShallow((state) => ({
|
|
17
22
|
setCleanUpStack: state.setCleanUpStack
|
|
@@ -37,7 +42,16 @@ function useFormListControl({ name, form, initialValues, formName }) {
|
|
|
37
42
|
value: d
|
|
38
43
|
};
|
|
39
44
|
}).filter(Boolean);
|
|
40
|
-
const mapCurWithKey = cur.map((c) =>
|
|
45
|
+
const mapCurWithKey = cur.map((c) => {
|
|
46
|
+
const find = mapPrevWithKey.find((m) => m.key === c.key);
|
|
47
|
+
if (find) {
|
|
48
|
+
return {
|
|
49
|
+
key: find.key,
|
|
50
|
+
value: isNil(c.value) ? find.value : c.value
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return c;
|
|
54
|
+
});
|
|
41
55
|
const getNewValueCache = mapCurWithKey.filter(Boolean).map((c) => c.value);
|
|
42
56
|
const startRemoveIndex = formDataBeforeChange.length - getNewValueCache.length;
|
|
43
57
|
if (startRemoveIndex > 0) {
|
|
@@ -218,6 +232,42 @@ function useFormListControl({ name, form, initialValues, formName }) {
|
|
|
218
232
|
clearCacheData();
|
|
219
233
|
};
|
|
220
234
|
}, [listFields]);
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (!getListener(formItemId)) {
|
|
237
|
+
setListener({
|
|
238
|
+
formName: formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName),
|
|
239
|
+
name: name || "",
|
|
240
|
+
formItemId,
|
|
241
|
+
type: "array",
|
|
242
|
+
onArrayChange: (newArr) => {
|
|
243
|
+
setListFields((prev) => {
|
|
244
|
+
const result = newArr.map((_, i) => {
|
|
245
|
+
const itemName = `${name}.${i}`;
|
|
246
|
+
const existingItem = prev[i];
|
|
247
|
+
return {
|
|
248
|
+
key: existingItem ? existingItem.key : v4(),
|
|
249
|
+
name: itemName
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
handleCacheListField(prev, result.map((r, i) => {
|
|
253
|
+
return { ...r, value: newArr[i] };
|
|
254
|
+
}));
|
|
255
|
+
return result;
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return () => {
|
|
261
|
+
if (getListener(formItemId)) {
|
|
262
|
+
setListener({
|
|
263
|
+
formName: formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName),
|
|
264
|
+
name: name || "",
|
|
265
|
+
formItemId,
|
|
266
|
+
onArrayChange: void 0
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}, []);
|
|
221
271
|
return { listFields, move, add, remove };
|
|
222
272
|
}
|
|
223
273
|
export {
|
package/dist/providers/Form.js
CHANGED
|
@@ -47,9 +47,36 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
|
|
|
47
47
|
const setFieldValue = (name, value, options) => {
|
|
48
48
|
const listener = getListeners().find((l) => l.name === name && l.formName === formName);
|
|
49
49
|
if (listener) {
|
|
50
|
-
listener.
|
|
50
|
+
if (listener.type === "array") {
|
|
51
|
+
if (!isEqual(getFormItemValue(formName, name), value)) {
|
|
52
|
+
listener.onArrayChange(value, options);
|
|
53
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
54
|
+
allStringPath.forEach((p) => {
|
|
55
|
+
const findListener = getListeners().find((l) => l.name === `${name}.${p}` && l.formName === formName);
|
|
56
|
+
if (findListener) {
|
|
57
|
+
findListener.onChange(get(value, p), options);
|
|
58
|
+
} else {
|
|
59
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
listener.onChange(value, options);
|
|
65
|
+
}
|
|
51
66
|
} else {
|
|
52
|
-
|
|
67
|
+
if (typeof value === "object" && !isNil(value)) {
|
|
68
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
69
|
+
allStringPath.forEach((p) => {
|
|
70
|
+
const findListener = getListeners().find((l) => l.name === `${name}.${p}` && l.formName === formName);
|
|
71
|
+
if (findListener) {
|
|
72
|
+
findListener.onChange(get(value, p), options);
|
|
73
|
+
} else {
|
|
74
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
setData(formName, name, value);
|
|
79
|
+
}
|
|
53
80
|
}
|
|
54
81
|
};
|
|
55
82
|
const setFieldValues = (values, options = { notTriggerDirty: false }) => {
|
|
@@ -57,9 +84,39 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
|
|
|
57
84
|
allStringPath.forEach((p) => {
|
|
58
85
|
const listener = getListeners().find((l) => l.name === p && l.formName === formName);
|
|
59
86
|
if (listener) {
|
|
60
|
-
listener.
|
|
87
|
+
if (listener.type === "array") {
|
|
88
|
+
if (!isEqual(getFormItemValue(formName, p), get(values, p))) {
|
|
89
|
+
listener.onArrayChange(get(values, listener.name), options);
|
|
90
|
+
const nestedAllStringPath = getAllNoneObjStringPath(get(values, p));
|
|
91
|
+
nestedAllStringPath.forEach((np) => {
|
|
92
|
+
{
|
|
93
|
+
const findListener = getListeners().find((l) => l.name === `${p}.${np}` && l.formName === formName);
|
|
94
|
+
if (findListener) {
|
|
95
|
+
findListener.onChange(get(values, `${p}.${np}`), options);
|
|
96
|
+
} else {
|
|
97
|
+
setData(formName, `${p}.${np}`, get(values, `${p}.${np}`));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
listener.onChange(get(values, listener.name), options);
|
|
104
|
+
}
|
|
61
105
|
} else {
|
|
62
|
-
|
|
106
|
+
if (typeof get(values, p) === "object" && !isNil(get(values, p))) {
|
|
107
|
+
const nestedAllStringPath = getAllNoneObjStringPath(get(values, p));
|
|
108
|
+
nestedAllStringPath.forEach((np) => {
|
|
109
|
+
{
|
|
110
|
+
const findListener = getListeners().find((l) => l.name === `${p}.${np}` && l.formName === formName);
|
|
111
|
+
if (findListener) {
|
|
112
|
+
findListener.onChange(get(values, `${p}.${np}`), options);
|
|
113
|
+
} else {
|
|
114
|
+
setData(formName, `${p}.${np}`, get(values, `${p}.${np}`));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
} else
|
|
119
|
+
setData(formName, p, get(values, p));
|
|
63
120
|
}
|
|
64
121
|
});
|
|
65
122
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type ListenerFormItemType = "normal" | "array";
|
|
1
2
|
export interface FormInstance {
|
|
2
3
|
formName: string;
|
|
3
4
|
resetFields: (values?: any) => void;
|
|
@@ -20,11 +21,13 @@ export interface ListenerItem {
|
|
|
20
21
|
isDirty?: boolean;
|
|
21
22
|
formItemId?: string;
|
|
22
23
|
internalErrors?: any;
|
|
24
|
+
onArrayChange?: any;
|
|
23
25
|
onChange?: any;
|
|
24
26
|
onReset?: any;
|
|
25
27
|
onFocus?: any;
|
|
26
28
|
emitFocus?: any;
|
|
27
29
|
isInitied?: boolean;
|
|
30
|
+
type?: ListenerFormItemType;
|
|
28
31
|
}
|
|
29
32
|
export interface CleanUpItem {
|
|
30
33
|
name?: string;
|
package/dist/stores/formStore.js
CHANGED
|
@@ -200,7 +200,7 @@ const createListenersSlice = (storeSet, storeGet, api) => ({
|
|
|
200
200
|
getListeners() {
|
|
201
201
|
return storeGet().listeners;
|
|
202
202
|
},
|
|
203
|
-
setListener({ formName, name, onChange, onReset, isTouched, isDirty, formItemId, internalErrors, onFocus, emitFocus, isInitied }) {
|
|
203
|
+
setListener({ formName, name, onChange, onReset, isTouched, isDirty, formItemId, internalErrors, onFocus, emitFocus, isInitied, type, onArrayChange }) {
|
|
204
204
|
return storeSet(produce((state) => {
|
|
205
205
|
const storeListeners = state.listeners;
|
|
206
206
|
const findListenerIndex = state.listeners.findIndex((l) => l.formItemId === formItemId);
|
|
@@ -235,6 +235,9 @@ const createListenersSlice = (storeSet, storeGet, api) => ({
|
|
|
235
235
|
if (!isNil(isInitied)) {
|
|
236
236
|
storeListeners[findListenerIndex].isInitied = isInitied;
|
|
237
237
|
}
|
|
238
|
+
if (!isNil(onArrayChange)) {
|
|
239
|
+
storeListeners[findListenerIndex].onArrayChange = onArrayChange;
|
|
240
|
+
}
|
|
238
241
|
return;
|
|
239
242
|
}
|
|
240
243
|
if (name && formName) {
|
|
@@ -247,7 +250,9 @@ const createListenersSlice = (storeSet, storeGet, api) => ({
|
|
|
247
250
|
internalErrors,
|
|
248
251
|
onChange,
|
|
249
252
|
onReset,
|
|
250
|
-
isInitied: Boolean(isInitied)
|
|
253
|
+
isInitied: Boolean(isInitied),
|
|
254
|
+
type: type || "normal",
|
|
255
|
+
onArrayChange
|
|
251
256
|
});
|
|
252
257
|
}
|
|
253
258
|
}));
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -47,7 +47,7 @@ const App = () => {
|
|
|
47
47
|
initialValues={{
|
|
48
48
|
TestData: "",
|
|
49
49
|
numericCode: "",
|
|
50
|
-
arr: [{ el: "Item 1" }, { el: "Item 2" }],
|
|
50
|
+
// arr: [{ el: "Item 1" }, { el: "Item 2" }],
|
|
51
51
|
}}
|
|
52
52
|
onFinish={(values) => {
|
|
53
53
|
console.log(values);
|
|
@@ -103,7 +103,8 @@ const App = () => {
|
|
|
103
103
|
<FormList
|
|
104
104
|
initialValues={[
|
|
105
105
|
{
|
|
106
|
-
el: "",
|
|
106
|
+
el: "sdfsdf",
|
|
107
|
+
d: { child: "Test Child" },
|
|
107
108
|
},
|
|
108
109
|
]}
|
|
109
110
|
name="arr"
|
|
@@ -112,7 +113,15 @@ const App = () => {
|
|
|
112
113
|
<div>
|
|
113
114
|
{fields.map((field, index) => (
|
|
114
115
|
<div key={field.key} style={{ marginBottom: 8 }}>
|
|
115
|
-
<FormItem name={`${field.name}.el`}>
|
|
116
|
+
<FormItem name={`${field.name}.el`} initialValue={"Chém gió"}>
|
|
117
|
+
<InputWrapper>
|
|
118
|
+
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
119
|
+
</InputWrapper>
|
|
120
|
+
</FormItem>
|
|
121
|
+
<FormItem
|
|
122
|
+
name={`${field.name}.d.child`}
|
|
123
|
+
initialValue={"Con của item"}
|
|
124
|
+
>
|
|
116
125
|
<InputWrapper>
|
|
117
126
|
<Input placeholder="Item value" style={{ width: 200 }} />
|
|
118
127
|
</InputWrapper>
|
|
@@ -145,6 +154,17 @@ const App = () => {
|
|
|
145
154
|
</div>
|
|
146
155
|
)}
|
|
147
156
|
</FormList>
|
|
157
|
+
<Button
|
|
158
|
+
onClick={() => {
|
|
159
|
+
form?.setFieldValue("arr", [
|
|
160
|
+
{ el: "Set Item 1" },
|
|
161
|
+
{ el: "Set Item 2" },
|
|
162
|
+
{ el: "Set Item 3" },
|
|
163
|
+
]);
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
Test set array list value
|
|
167
|
+
</Button>
|
|
148
168
|
<motion.div
|
|
149
169
|
initial={{ opacity: 0 }}
|
|
150
170
|
animate={{ opacity: 1 }}
|
|
@@ -9,7 +9,7 @@ export interface FormListProps<T = any> {
|
|
|
9
9
|
children: (
|
|
10
10
|
fields: Array<{ name: string; key: string }>,
|
|
11
11
|
operations: {
|
|
12
|
-
add: (index
|
|
12
|
+
add: (index?: number) => void;
|
|
13
13
|
remove: (opts: { index?: number; key?: string }) => void;
|
|
14
14
|
move: (opts: { from?: number; fromKey?: string; to: number }) => void;
|
|
15
15
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { get, isNil } from "lodash";
|
|
1
|
+
import { get, has, isNil } from "lodash";
|
|
2
2
|
import { useTaskEffect } from "minh-custom-hooks-release";
|
|
3
3
|
import { useEffect, useMemo, type RefObject } from "react";
|
|
4
4
|
import { useShallow } from "zustand/react/shallow"; // Import useShallow
|
|
@@ -595,13 +595,17 @@ export default function useFormItemControl<T = any>({
|
|
|
595
595
|
// console.log("Get cache Data after list change: ", cacheData);
|
|
596
596
|
|
|
597
597
|
if (cacheData) {
|
|
598
|
+
console.log("Cache data found when form item change: ", name, cacheData);
|
|
598
599
|
const getNewDataFromCache = get(cacheData, name);
|
|
600
|
+
const isIncludeDirectoryInCache = has(cacheData, name);
|
|
599
601
|
|
|
600
602
|
// console.log("Init data when change form ite: ", name, cacheData);
|
|
601
603
|
|
|
602
|
-
if (!getNewDataFromCache) {
|
|
603
|
-
onChange(initialValue
|
|
604
|
-
|
|
604
|
+
if (!isIncludeDirectoryInCache && isNil(getNewDataFromCache)) {
|
|
605
|
+
onChange(initialValue, {
|
|
606
|
+
notTriggerDirty: true,
|
|
607
|
+
});
|
|
608
|
+
} else onChange(getNewDataFromCache, { notTriggerDirty: true });
|
|
605
609
|
}
|
|
606
610
|
}, [name, formName || form?.formName || contextForm?.formName]);
|
|
607
611
|
|
|
@@ -7,7 +7,7 @@ import { useFormStore } from "../stores/formStore";
|
|
|
7
7
|
|
|
8
8
|
import type { FormInstance } from "../stores/formStore";
|
|
9
9
|
|
|
10
|
-
type ListField = { name: string; key: string };
|
|
10
|
+
type ListField = { name: string; key: string; value?: any };
|
|
11
11
|
|
|
12
12
|
interface UseFormListControlProps {
|
|
13
13
|
name?: string;
|
|
@@ -29,18 +29,25 @@ export default function useFormListControl<T = any>({
|
|
|
29
29
|
initialValues,
|
|
30
30
|
formName,
|
|
31
31
|
}: UseFormListControlProps): UseFormListControlReturn {
|
|
32
|
+
const [formItemId] = useState<string>(v4());
|
|
32
33
|
const contextForm = useFormContext();
|
|
33
34
|
const getFormValues = useFormStore((state) => state.getFormValues);
|
|
34
35
|
const [listFormInitValues, setListFormInitValues] = useState<
|
|
35
36
|
any[] | undefined
|
|
36
37
|
>(undefined);
|
|
37
|
-
const { clearCacheData, setCacheData } =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const { clearCacheData, setCacheData, setListener, getListener } =
|
|
39
|
+
useFormStore(
|
|
40
|
+
useShallow((state) => ({
|
|
41
|
+
// Cache
|
|
42
|
+
cacheData: state.cacheData,
|
|
43
|
+
clearCacheData: state.clearCacheData,
|
|
44
|
+
setCacheData: state.setCacheData,
|
|
45
|
+
|
|
46
|
+
// Listener
|
|
47
|
+
setListener: state.setListener,
|
|
48
|
+
getListener: state.getListener,
|
|
49
|
+
})),
|
|
50
|
+
);
|
|
44
51
|
const { setCleanUpStack } = useFormStore(
|
|
45
52
|
useShallow((state) => ({
|
|
46
53
|
setCleanUpStack: state.setCleanUpStack,
|
|
@@ -94,9 +101,20 @@ export default function useFormListControl<T = any>({
|
|
|
94
101
|
})
|
|
95
102
|
.filter(Boolean);
|
|
96
103
|
|
|
97
|
-
const mapCurWithKey = cur.map(
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
const mapCurWithKey = cur.map((c) => {
|
|
105
|
+
const find = mapPrevWithKey.find((m) => m.key === c.key);
|
|
106
|
+
|
|
107
|
+
if (find) {
|
|
108
|
+
return {
|
|
109
|
+
key: find.key,
|
|
110
|
+
value: isNil(c.value) ? find.value : c.value,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return c;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
console.log("compare prev cur", { prev, cur });
|
|
100
118
|
|
|
101
119
|
const getNewValueCache = mapCurWithKey.filter(Boolean).map((c) => c.value);
|
|
102
120
|
|
|
@@ -109,7 +127,7 @@ export default function useFormListControl<T = any>({
|
|
|
109
127
|
// console.log("Mapping Cur value with prev fields: ", mapCurWithKey);
|
|
110
128
|
// console.log("After change arr value: ", getNewValueCache);
|
|
111
129
|
|
|
112
|
-
// Nếu số phần tử trước khi thay đổi mảng lớn hơn thì đẩy
|
|
130
|
+
// Nếu số phần tử trước khi thay đổi mảng lớn hơn thì đẩy các phần tử còn lại vào clean up stack để clear
|
|
113
131
|
if (startRemoveIndex > 0) {
|
|
114
132
|
Array.from(Array(startRemoveIndex))
|
|
115
133
|
.map((_, index) => {
|
|
@@ -129,6 +147,12 @@ export default function useFormListControl<T = any>({
|
|
|
129
147
|
});
|
|
130
148
|
}
|
|
131
149
|
|
|
150
|
+
console.log("Set cache data for form list: ", {
|
|
151
|
+
formName: formName || form?.formName || contextForm?.formName,
|
|
152
|
+
name,
|
|
153
|
+
getNewValueCache,
|
|
154
|
+
});
|
|
155
|
+
|
|
132
156
|
// console.log({ getNewValueCache });
|
|
133
157
|
setCacheData(
|
|
134
158
|
formName || form?.formName || contextForm?.formName,
|
|
@@ -228,7 +252,7 @@ export default function useFormListControl<T = any>({
|
|
|
228
252
|
to: number;
|
|
229
253
|
}) => {
|
|
230
254
|
setListFields((prev) => {
|
|
231
|
-
console.log("move list item: ", { from, to });
|
|
255
|
+
// console.log("move list item: ", { from, to });
|
|
232
256
|
if (
|
|
233
257
|
from >= listFields.length ||
|
|
234
258
|
from < 0 ||
|
|
@@ -237,7 +261,7 @@ export default function useFormListControl<T = any>({
|
|
|
237
261
|
from === to
|
|
238
262
|
)
|
|
239
263
|
return prev;
|
|
240
|
-
console.log("Trigger move item: ");
|
|
264
|
+
// console.log("Trigger move item: ");
|
|
241
265
|
|
|
242
266
|
if (!isNil(fromKey)) {
|
|
243
267
|
const findItemIndex = prev.findIndex((p) => p.key === fromKey);
|
|
@@ -383,5 +407,50 @@ export default function useFormListControl<T = any>({
|
|
|
383
407
|
};
|
|
384
408
|
}, [listFields]);
|
|
385
409
|
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (!getListener(formItemId)) {
|
|
412
|
+
setListener({
|
|
413
|
+
formName: formName || form?.formName || contextForm?.formName,
|
|
414
|
+
name: name || "",
|
|
415
|
+
formItemId: formItemId,
|
|
416
|
+
type: "array",
|
|
417
|
+
onArrayChange: (newArr) => {
|
|
418
|
+
setListFields((prev) => {
|
|
419
|
+
const result = newArr.map((_, i) => {
|
|
420
|
+
const itemName = `${name}.${i}`;
|
|
421
|
+
const existingItem = prev[i];
|
|
422
|
+
return {
|
|
423
|
+
key: existingItem ? existingItem.key : v4(),
|
|
424
|
+
name: itemName,
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
handleCacheListField(
|
|
429
|
+
prev,
|
|
430
|
+
result.map((r, i) => {
|
|
431
|
+
return { ...r, value: newArr[i] };
|
|
432
|
+
}),
|
|
433
|
+
);
|
|
434
|
+
return result;
|
|
435
|
+
});
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return () => {
|
|
440
|
+
// Remove listener on unmount
|
|
441
|
+
if (getListener(formItemId)) {
|
|
442
|
+
console.log("Remove listener for form list: ", {
|
|
443
|
+
formItemId,
|
|
444
|
+
});
|
|
445
|
+
setListener({
|
|
446
|
+
formName: formName || form?.formName || contextForm?.formName,
|
|
447
|
+
name: name || "",
|
|
448
|
+
formItemId: formItemId,
|
|
449
|
+
onArrayChange: undefined,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}, []);
|
|
454
|
+
|
|
386
455
|
return { listFields, move, add, remove };
|
|
387
456
|
}
|
package/src/providers/Form.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import { flushSync } from "react-dom";
|
|
|
6
6
|
import { useShallow } from "zustand/react/shallow"; // Import useShallow
|
|
7
7
|
import FormCleanUp from "../components/Form/FormCleanUp";
|
|
8
8
|
import { SUBMIT_STATE } from "../constants/form";
|
|
9
|
-
import { useFormStore } from "../stores/formStore";
|
|
9
|
+
import { ListenerItem, useFormStore } from "../stores/formStore";
|
|
10
10
|
import type {
|
|
11
11
|
PublicFormInstance,
|
|
12
12
|
UseFormItemStateWatchReturn,
|
|
@@ -90,13 +90,51 @@ export default function Form<T = any>({
|
|
|
90
90
|
);
|
|
91
91
|
|
|
92
92
|
const setFieldValue = (name, value, options) => {
|
|
93
|
-
const listener = getListeners().find(
|
|
93
|
+
const listener: ListenerItem | null = getListeners().find(
|
|
94
94
|
(l) => l.name === name && l.formName === formName,
|
|
95
95
|
);
|
|
96
96
|
if (listener) {
|
|
97
|
-
listener
|
|
97
|
+
// Nếu loại listener là array thì gọi onArrayChange
|
|
98
|
+
if (listener.type === "array") {
|
|
99
|
+
// Do nothing if the value is the same for array item to prevent unnecessary re-renders
|
|
100
|
+
if (!isEqual(getFormItemValue(formName, name), value)) {
|
|
101
|
+
listener.onArrayChange(value, options);
|
|
102
|
+
|
|
103
|
+
// 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 đó
|
|
104
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
105
|
+
allStringPath.forEach((p) => {
|
|
106
|
+
const findListener = getListeners().find(
|
|
107
|
+
(l) => l.name === `${name}.${p}` && l.formName === formName,
|
|
108
|
+
);
|
|
109
|
+
if (findListener) {
|
|
110
|
+
findListener.onChange(get(value, p), options);
|
|
111
|
+
} else {
|
|
112
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
listener.onChange(value, options);
|
|
118
|
+
}
|
|
98
119
|
} else {
|
|
99
|
-
|
|
120
|
+
// set data for non-listener field
|
|
121
|
+
if (typeof value === "object" && !isNil(value)) {
|
|
122
|
+
// Nếu là object hoặc array thì set từng path con
|
|
123
|
+
const allStringPath = getAllNoneObjStringPath(value);
|
|
124
|
+
|
|
125
|
+
allStringPath.forEach((p) => {
|
|
126
|
+
const findListener = getListeners().find(
|
|
127
|
+
(l) => l.name === `${name}.${p}` && l.formName === formName,
|
|
128
|
+
);
|
|
129
|
+
if (findListener) {
|
|
130
|
+
findListener.onChange(get(value, p), options);
|
|
131
|
+
} else {
|
|
132
|
+
setData(formName, `${name}.${p}`, get(value, p));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
setData(formName, name, value);
|
|
137
|
+
}
|
|
100
138
|
}
|
|
101
139
|
};
|
|
102
140
|
|
|
@@ -108,9 +146,46 @@ export default function Form<T = any>({
|
|
|
108
146
|
(l) => l.name === p && l.formName === formName,
|
|
109
147
|
);
|
|
110
148
|
if (listener) {
|
|
111
|
-
listener.
|
|
149
|
+
if (listener.type === "array") {
|
|
150
|
+
// Do nothing if the value is the same for array item to prevent unnecessary re-renders
|
|
151
|
+
if (!isEqual(getFormItemValue(formName, p), get(values, p))) {
|
|
152
|
+
listener.onArrayChange(get(values, listener.name), options);
|
|
153
|
+
|
|
154
|
+
// 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 đó
|
|
155
|
+
const nestedAllStringPath = getAllNoneObjStringPath(get(values, p));
|
|
156
|
+
nestedAllStringPath.forEach((np) => {
|
|
157
|
+
{
|
|
158
|
+
const findListener = getListeners().find(
|
|
159
|
+
(l) => l.name === `${p}.${np}` && l.formName === formName,
|
|
160
|
+
);
|
|
161
|
+
if (findListener) {
|
|
162
|
+
findListener.onChange(get(values, `${p}.${np}`), options);
|
|
163
|
+
} else {
|
|
164
|
+
setData(formName, `${p}.${np}`, get(values, `${p}.${np}`));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
listener.onChange(get(values, listener.name), options);
|
|
171
|
+
}
|
|
112
172
|
} else {
|
|
113
|
-
|
|
173
|
+
// Kiểm tra nếu là object và có listener con thì gọi onChange của listener con
|
|
174
|
+
if (typeof get(values, p) === "object" && !isNil(get(values, p))) {
|
|
175
|
+
const nestedAllStringPath = getAllNoneObjStringPath(get(values, p));
|
|
176
|
+
nestedAllStringPath.forEach((np) => {
|
|
177
|
+
{
|
|
178
|
+
const findListener = getListeners().find(
|
|
179
|
+
(l) => l.name === `${p}.${np}` && l.formName === formName,
|
|
180
|
+
);
|
|
181
|
+
if (findListener) {
|
|
182
|
+
findListener.onChange(get(values, `${p}.${np}`), options);
|
|
183
|
+
} else {
|
|
184
|
+
setData(formName, `${p}.${np}`, get(values, `${p}.${np}`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
} else setData(formName, p, get(values, p));
|
|
114
189
|
}
|
|
115
190
|
});
|
|
116
191
|
};
|
package/src/stores/formStore.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { cloneDeep, get, isNil, isNumber, last, set, unset } from "lodash";
|
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import { create } from "zustand";
|
|
5
5
|
import { getAllNoneObjStringPath } from "../utils/obj.util";
|
|
6
|
-
|
|
6
|
+
type ListenerFormItemType = "normal" | "array";
|
|
7
7
|
export interface FormInstance {
|
|
8
8
|
formName: string;
|
|
9
9
|
resetFields: (values?: any) => void;
|
|
@@ -24,11 +24,13 @@ export interface ListenerItem {
|
|
|
24
24
|
isDirty?: boolean;
|
|
25
25
|
formItemId?: string;
|
|
26
26
|
internalErrors?: any;
|
|
27
|
+
onArrayChange?: any;
|
|
27
28
|
onChange?: any;
|
|
28
29
|
onReset?: any;
|
|
29
30
|
onFocus?: any;
|
|
30
31
|
emitFocus?: any;
|
|
31
32
|
isInitied?: boolean;
|
|
33
|
+
type?: ListenerFormItemType;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export interface CleanUpItem {
|
|
@@ -379,7 +381,9 @@ const createListenersSlice = (storeSet: any, storeGet: any, api: any) => ({
|
|
|
379
381
|
onFocus,
|
|
380
382
|
emitFocus,
|
|
381
383
|
isInitied,
|
|
382
|
-
|
|
384
|
+
type,
|
|
385
|
+
onArrayChange,
|
|
386
|
+
}: Partial<ListenerItem> & { formItemId: string }) {
|
|
383
387
|
return storeSet(
|
|
384
388
|
produce<any>((state: any) => {
|
|
385
389
|
const storeListeners = state.listeners;
|
|
@@ -420,6 +424,10 @@ const createListenersSlice = (storeSet: any, storeGet: any, api: any) => ({
|
|
|
420
424
|
storeListeners[findListenerIndex].isInitied = isInitied;
|
|
421
425
|
}
|
|
422
426
|
|
|
427
|
+
if (!isNil(onArrayChange)) {
|
|
428
|
+
storeListeners[findListenerIndex].onArrayChange = onArrayChange;
|
|
429
|
+
}
|
|
430
|
+
|
|
423
431
|
return;
|
|
424
432
|
}
|
|
425
433
|
if (name && formName) {
|
|
@@ -433,6 +441,8 @@ const createListenersSlice = (storeSet: any, storeGet: any, api: any) => ({
|
|
|
433
441
|
onChange,
|
|
434
442
|
onReset,
|
|
435
443
|
isInitied: Boolean(isInitied),
|
|
444
|
+
type: type || "normal",
|
|
445
|
+
onArrayChange,
|
|
436
446
|
});
|
|
437
447
|
}
|
|
438
448
|
}),
|