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.
@@ -8,7 +8,7 @@ export interface FormListProps<T = any> {
8
8
  name: string;
9
9
  key: string;
10
10
  }>, operations: {
11
- add: (index: number) => void;
11
+ add: (index?: number) => void;
12
12
  remove: (opts: {
13
13
  index?: number;
14
14
  key?: string;
@@ -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
- if (!getNewDataFromCache) {
323
- onChange(initialValue);
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(() => {
@@ -2,6 +2,7 @@ import type { FormInstance } from "../stores/formStore";
2
2
  type ListField = {
3
3
  name: string;
4
4
  key: string;
5
+ value?: any;
5
6
  };
6
7
  interface UseFormListControlProps {
7
8
  name?: string;
@@ -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) => mapPrevWithKey.find((m) => m.key === c.key) || 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 {
@@ -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.onChange(value, options);
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
- setData(formName, name, value);
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.onChange(get(values, listener.name), options);
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
- setData(formName, p, get(values, p));
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;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-form-manage",
3
- "version": "1.0.8-beta.16",
3
+ "version": "1.0.8-beta.17",
4
4
  "description": "Lightweight React form management with list and listener support.",
5
5
  "license": "MIT",
6
6
  "type": "module",
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: number) => void;
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
- } else onChange(getNewDataFromCache);
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 } = useFormStore(
38
- useShallow((state) => ({
39
- cacheData: state.cacheData,
40
- clearCacheData: state.clearCacheData,
41
- setCacheData: state.setCacheData,
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
- (c) => mapPrevWithKey.find((m) => m.key === c.key) || c,
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 2 phần tử còn lại vào clean up stack để clear
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
  }
@@ -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.onChange(value, options);
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
- setData(formName, name, value);
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.onChange(get(values, listener.name), options);
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
- setData(formName, p, get(values, p));
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
  };
@@ -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
  }),