react-form-manage 1.0.8-beta.10 → 1.0.8-beta.12

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 CHANGED
@@ -15,6 +15,11 @@ Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
15
15
 
16
16
  - Add isTouched field to FormItem for tracking user interaction state
17
17
 
18
+ ## [1.0.8-beta.10] - 2026-01-27
19
+ - `useForm()` giờ có thể trả về form instance từ provider lân cận nếu không truyền `formName` (không còn bắt buộc truyền tên form khi đã wrap trong `Form`).
20
+ - `FormList.add` khi không truyền `index` sẽ mặc định append vào cuối danh sách.
21
+ - Docs: cập nhật hướng dẫn cho `useForm` và `FormList.add`.
22
+
18
23
  ## [1.0.8-beta.1] - 2026-01-22
19
24
 
20
25
  - Fix UseFormItemControlReturn errors type to properly export FormFieldError[]
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Thư viện form nhẹ dành cho React — hỗ trợ form, field, list (array fields), và cơ chế listener để giảm re-render.
4
4
 
5
- Phiên bản này xuất phát từ mã nguồn JS. Typings (.d.ts) được cung cấp trong `src/types/components.d.ts`.
6
-
7
5
  ---
8
6
 
9
7
  ## Cài đặt
@@ -80,6 +78,8 @@ export default Example;
80
78
 
81
79
  Bạn có thể lấy instance form để gọi phương thức chương trình như `submit`, `resetFields`, `setFieldValue`…
82
80
 
81
+ `useForm` ưu tiên lấy instance từ provider gần nhất nếu không truyền `formName`, giúp bạn không cần truyền tên form khi đã bọc trong `Form`.
82
+
83
83
  Ví dụ sử dụng hook attached trên `Form`:
84
84
 
85
85
  ```jsx
@@ -87,7 +87,7 @@ import React from "react";
87
87
  import Form from "react-form-manage";
88
88
 
89
89
  function CtrlExample() {
90
- const [form] = Form.useForm("myForm"); // hoặc useForm('myForm')
90
+ const [form] = Form.useForm(); // hoặc Form.useForm('myForm') khi muốn chỉ định form khác
91
91
 
92
92
  return (
93
93
  <div>
@@ -156,6 +156,9 @@ Custom validator (async):
156
156
 
157
157
  `FormList` là render-prop component cung cấp `listFields` và các action `add`, `remove`, `move`.
158
158
 
159
+ Ghi chú:
160
+ - `add()` nếu không truyền `index` sẽ tự động append phần tử mới vào cuối danh sách.
161
+
159
162
  Ví dụ:
160
163
 
161
164
  ```jsx
@@ -192,7 +195,7 @@ Lưu ý: `FormList` quản lý các `key` nội bộ và push các mục cần c
192
195
 
193
196
  ## Hooks (tóm tắt)
194
197
 
195
- - `useForm(formNameOrFormInstance)` — trả về form instance đã đăng ký.
198
+ - `useForm(formNameOrFormInstance?)` — nếu không truyền thì lấy instance từ `Form` provider gần nhất; nếu truyền `formName` thì chọn form tương ứng.
196
199
  - `useWatch(name, formNameOrFormInstance)` — subscribe giá trị field.
197
200
  - `useSubmitDataWatch` — watch dữ liệu submit cuối cùng.
198
201
  - `useFormStateWatch` — watch state của form (isInitied, submitState…).
@@ -1,13 +1,13 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect } from "react";
3
3
  import { useShallow } from "zustand/react/shallow";
4
- import { useFormCleanUp, useFormListeners, useFormStore } from "../../stores/formStore";
4
+ import { useFormStore } from "../../stores/formStore";
5
5
  const FormCleanUp = () => {
6
- const { cleanUpStack, clearCleanUpStack } = useFormCleanUp(useShallow((state) => ({
6
+ const { cleanUpStack, clearCleanUpStack } = useFormStore(useShallow((state) => ({
7
7
  cleanUpStack: state.cleanUpStack,
8
8
  clearCleanUpStack: state.clearCleanUpStack
9
9
  })));
10
- const { revokeListener } = useFormListeners(useShallow((state) => ({
10
+ const { revokeListener } = useFormStore(useShallow((state) => ({
11
11
  revokeListener: state.revokeListener
12
12
  })));
13
13
  const { clearObjKeyItem, clearArrItem } = useFormStore(useShallow((state) => {
@@ -20,5 +20,5 @@ export interface FormListProps<T = any> {
20
20
  }) => void;
21
21
  }) => React.ReactNode;
22
22
  }
23
- declare const FormList: <T = any>({ name, initialValues, form, formName, children }: FormListProps<T>) => import("react").ReactNode;
23
+ declare const FormList: <T = any>({ name, initialValues, form, formName, children, }: FormListProps<T>) => import("react").ReactNode;
24
24
  export default FormList;
@@ -1,6 +1,6 @@
1
- import useTestRrenderListControl from "../../hooks/useFormListControl";
1
+ import useFormListControl from "../../hooks/useFormListControl";
2
2
  const FormList = ({ name, initialValues, form, formName, children }) => {
3
- const { listFields, ...actions } = useTestRrenderListControl({
3
+ const { listFields, ...actions } = useFormListControl({
4
4
  name,
5
5
  initialValues,
6
6
  form,
@@ -4,7 +4,7 @@ import { useEffect, useMemo } from "react";
4
4
  import { useShallow } from "zustand/react/shallow";
5
5
  import { IS_ALPHABET_STRING_AND_NUMBER_REGEX, IS_EMAIL_REGEX, IS_NAME_REGEX, IS_NO_SPACE_ALPHABET_STRING_AND_NUMBER_REGEX, IS_NO_SPACE_ALPHABET_STRING_REGEX, IS_NOSPACE_STRING_AND_NUMBER_REGEX, IS_PASSWORD_REGEX, IS_POSITIVE_INTEGER_STRING_NUMBER_REGEX, IS_POSITIVE_STRING_NUMBER_REGEX, IS_STRING_AND_NUMBER_REGEX, IS_STRING_NUMBER_REGEX, IS_USERNAME_REGEX, IS_VIETNAMESE_PHONE_NUMBER_REGEX } from "../constants/validation";
6
6
  import { useFormContext } from "../providers/Form";
7
- import { useFormCleanUp, useFormListeners, useFormStore } from "../stores/formStore";
7
+ import { useFormStore } from "../stores/formStore";
8
8
  const VALID_PREMITIVE_TYPE = ["string", "number", "undefined"];
9
9
  function useFormItemControl({ formName, form, name, initialValue, formItemId, rules, elementRef }) {
10
10
  const contextForm = useFormContext();
@@ -20,7 +20,7 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
20
20
  submitState: (_d = (_c = state2.formStates) == null ? void 0 : _c[formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName)]) == null ? void 0 : _d.submitState
21
21
  };
22
22
  }));
23
- const { setCleanUpStack } = useFormCleanUp(useShallow((state2) => ({
23
+ const { setCleanUpStack } = useFormStore(useShallow((state2) => ({
24
24
  setCleanUpStack: state2.setCleanUpStack
25
25
  })));
26
26
  const { initValue: internalInitValue, setInitData, getInitData } = useFormStore(useShallow((state2) => {
@@ -30,7 +30,7 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
30
30
  getInitData: state2.getInitData
31
31
  };
32
32
  }));
33
- const { listener, setListener } = useFormListeners(useShallow((state2) => {
33
+ const { listener, setListener } = useFormStore(useShallow((state2) => {
34
34
  return {
35
35
  listener: state2.listeners.find((l) => l.formItemId === formItemId),
36
36
  setListener: state2.setListener
@@ -16,7 +16,7 @@ interface UseFormListControlReturn {
16
16
  fromKey?: string;
17
17
  to: number;
18
18
  }) => void;
19
- add: (index: number) => void;
19
+ add: (index?: number) => void;
20
20
  remove: (opts: {
21
21
  index?: number;
22
22
  key?: string;
@@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
3
3
  import { v4 } from "uuid";
4
4
  import { useShallow } from "zustand/react/shallow";
5
5
  import { useFormContext } from "../providers/Form";
6
- import { useFormCleanUp, useFormStore } from "../stores/formStore";
6
+ import { useFormStore } from "../stores/formStore";
7
7
  function useFormListControl({ name, form, initialValues, formName }) {
8
8
  const contextForm = useFormContext();
9
9
  const getFormValues = useFormStore((state) => state.getFormValues);
@@ -13,7 +13,7 @@ function useFormListControl({ name, form, initialValues, formName }) {
13
13
  clearCacheData: state.clearCacheData,
14
14
  setCacheData: state.setCacheData
15
15
  })));
16
- const { setCleanUpStack } = useFormCleanUp(useShallow((state) => ({
16
+ const { setCleanUpStack } = useFormStore(useShallow((state) => ({
17
17
  setCleanUpStack: state.setCleanUpStack
18
18
  })));
19
19
  const { initValue: internalInitValue, formState } = useFormStore(useShallow((state) => {
@@ -55,11 +55,7 @@ function useFormListControl({ name, form, initialValues, formName }) {
55
55
  };
56
56
  const add = (index) => {
57
57
  setListFields((prev) => {
58
- if (index > prev.length)
59
- return prev;
60
- if (index < 0)
61
- return prev;
62
- if (index === prev.length) {
58
+ if (isNil(index) || index === prev.length) {
63
59
  const newName = `${name}.${prev.length}`;
64
60
  const newKey = v4();
65
61
  const result2 = [
@@ -71,6 +67,10 @@ function useFormListControl({ name, form, initialValues, formName }) {
71
67
  ];
72
68
  return result2;
73
69
  }
70
+ if (index > prev.length)
71
+ return prev;
72
+ if (index < 0)
73
+ return prev;
74
74
  const clonePrev = [...prev];
75
75
  const result = clonePrev.reduce((prev2, cur, curIndex) => {
76
76
  const newKey = v4();
package/dist/index.d.ts CHANGED
@@ -6,5 +6,6 @@ import Input from "./components/Input";
6
6
  import InputWrapper, { type InputWrapperProps } from "./components/Form/InputWrapper";
7
7
  import useFormItemControl from "./hooks/useFormItemControl";
8
8
  import useFormListControl from "./hooks/useFormListControl";
9
- export { Form, FormItem, FormList, Input, InputWrapper, useFormItemControl, useFormListControl, useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps, type FormItemProps, type FormListProps, type InputWrapperProps, type ValidationRule, type FormFieldError, type SubmitState, type UseFormItemStateWatchReturn, SUBMIT_STATE, };
9
+ import { useFormStore, type FormInstance, type ListenerItem, type CleanUpItem } from "./stores/formStore";
10
+ export { Form, FormItem, FormList, Input, InputWrapper, useFormItemControl, useFormListControl, useForm, useWatch, useSubmitDataWatch, useFormStateWatch, useFormStore, type FormProps, type FormItemProps, type FormListProps, type InputWrapperProps, type ValidationRule, type FormFieldError, type SubmitState, type UseFormItemStateWatchReturn, type FormInstance, type ListenerItem, type CleanUpItem, SUBMIT_STATE, };
10
11
  export default Form;
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import Input from "./components/Input";
6
6
  import InputWrapper from "./components/Form/InputWrapper";
7
7
  import useFormItemControl from "./hooks/useFormItemControl";
8
8
  import useFormListControl from "./hooks/useFormListControl";
9
+ import { useFormStore } from "./stores/formStore";
9
10
  var stdin_default = Form;
10
11
  export {
11
12
  Form,
@@ -19,6 +20,7 @@ export {
19
20
  useFormItemControl,
20
21
  useFormListControl,
21
22
  useFormStateWatch,
23
+ useFormStore,
22
24
  useSubmitDataWatch,
23
25
  useWatch
24
26
  };
@@ -22,6 +22,9 @@ declare namespace Form {
22
22
  var useSubmitDataWatch: typeof import("./Form").useSubmitDataWatch;
23
23
  var useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
24
24
  var useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
25
+ var useWatchFormListeners: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => {
26
+ listeners: import("../stores/formStore").ListenerItem[];
27
+ };
25
28
  }
26
29
  export default Form;
27
30
  export declare function useFormContext(): any;
@@ -34,3 +37,6 @@ export declare function useSubmitDataWatch<T = any>({ formNameOrFormInstance, tr
34
37
  }): T | undefined;
35
38
  export declare const useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
36
39
  export declare const useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
40
+ export declare const useWatchFormListeners: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => {
41
+ listeners: import("../stores/formStore").ListenerItem[];
42
+ };
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cloneDeep, get, isEqual, last, set, uniq } from "lodash";
2
+ import { cloneDeep, get, isEqual, isNil, last, set, uniqBy } from "lodash";
3
3
  import { useTask } from "minh-custom-hooks-release";
4
4
  import { createContext, useContext, useEffect, useState } from "react";
5
5
  import { flushSync } from "react-dom";
6
6
  import { useShallow } from "zustand/react/shallow";
7
7
  import FormCleanUp from "../components/Form/FormCleanUp";
8
8
  import { SUBMIT_STATE } from "../constants/form";
9
- import { useFormListeners, useFormStore } from "../stores/formStore";
9
+ import { useFormStore } from "../stores/formStore";
10
10
  import { getAllNoneObjStringPath } from "../utils/obj.util";
11
11
  const FormContext = createContext(null);
12
12
  function Form({ children, formName, initialValues, onFinish, onReject, onFinally, FormElement, ...props }) {
@@ -39,7 +39,7 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
39
39
  // Test, nhớ xóa sau khi xong
40
40
  // formStates: state.formStates?.[formName],
41
41
  })));
42
- const { getListeners } = useFormListeners(useShallow((state) => {
42
+ const { getListeners } = useFormStore(useShallow((state) => {
43
43
  return {
44
44
  getListeners: state.getListeners
45
45
  };
@@ -106,7 +106,7 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
106
106
  const formValues = getFormValues(formName);
107
107
  const resultValues = cloneDeep(formValues);
108
108
  const cleanValues = {};
109
- uniq(listeners, (l) => l.name).forEach((l) => {
109
+ uniqBy(listeners, (l) => l.name).forEach((l) => {
110
110
  set(cleanValues, l.name, get(resultValues, l.name));
111
111
  });
112
112
  const handleIsolateCase = async () => {
@@ -264,9 +264,10 @@ function useFormContext() {
264
264
  return c;
265
265
  }
266
266
  function useForm(formNameOrFormInstance) {
267
- const targetFormName = typeof formNameOrFormInstance === "object" && formNameOrFormInstance !== null ? formNameOrFormInstance.formName : formNameOrFormInstance;
267
+ const formContext = useContext(FormContext);
268
+ const targetFormName = isNil(formNameOrFormInstance) ? formContext == null ? void 0 : formContext.formName : typeof formNameOrFormInstance === "object" && formNameOrFormInstance !== null ? formNameOrFormInstance.formName : formNameOrFormInstance;
268
269
  const formInstance = useFormStore((state) => {
269
- return state.formInstances.find((i) => i.formName === targetFormName);
270
+ return Object.values(state.formInstances).find((i) => i.formName === targetFormName);
270
271
  });
271
272
  return [formInstance];
272
273
  }
@@ -310,11 +311,22 @@ const useFormItemStateWatch = (nameOrFormItemId, formNameOrFormInstance) => {
310
311
  errors: (listener == null ? void 0 : listener.internalErrors) || []
311
312
  };
312
313
  };
314
+ const useWatchFormListeners = (formNameOrFormInstance) => {
315
+ const [formInstance] = useForm(formNameOrFormInstance);
316
+ const listeners = useFormStore(useShallow((state) => {
317
+ const allListeners = state.listeners;
318
+ return {
319
+ listeners: allListeners.filter((l) => !isNil(formNameOrFormInstance) ? true : l.formName === (formInstance == null ? void 0 : formInstance.formName))
320
+ };
321
+ }));
322
+ return listeners;
323
+ };
313
324
  Form.useForm = useForm;
314
325
  Form.useWatch = useWatch;
315
326
  Form.useSubmitDataWatch = useSubmitDataWatch;
316
327
  Form.useFormStateWatch = useFormStateWatch;
317
328
  Form.useFormItemStateWatch = useFormItemStateWatch;
329
+ Form.useWatchFormListeners = useWatchFormListeners;
318
330
  export {
319
331
  FormContext,
320
332
  Form as default,
@@ -323,5 +335,6 @@ export {
323
335
  useFormItemStateWatch,
324
336
  useFormStateWatch,
325
337
  useSubmitDataWatch,
326
- useWatch
338
+ useWatch,
339
+ useWatchFormListeners
327
340
  };
@@ -13,16 +13,38 @@ export interface FormInstance {
13
13
  getFieldErrors: () => Record<string, any>;
14
14
  setFieldFocus: (name: string) => void;
15
15
  }
16
+ export interface ListenerItem {
17
+ name?: string;
18
+ formName?: string;
19
+ isTouched?: boolean;
20
+ isDirty?: boolean;
21
+ formItemId?: string;
22
+ internalErrors?: any;
23
+ onChange?: any;
24
+ onReset?: any;
25
+ onFocus?: any;
26
+ emitFocus?: any;
27
+ isInitied?: boolean;
28
+ }
29
+ export interface CleanUpItem {
30
+ name?: string;
31
+ type?: string;
32
+ key: string;
33
+ itemKey?: string;
34
+ formName?: string;
35
+ }
16
36
  interface FormStoreState {
17
37
  forms: Record<string, any>;
18
38
  initialValues: Record<string, any>;
19
39
  formStates: Record<string, any>;
20
40
  cacheData: Record<string, any>;
21
- formInstances: FormInstance[];
41
+ formInstances: Record<string, FormInstance>;
22
42
  submitHistory: Record<string, any[]>;
43
+ listeners: ListenerItem[];
44
+ cleanUpStack: CleanUpItem[];
23
45
  [key: string]: any;
24
46
  }
25
47
  export declare const useFormStore: import("zustand").UseBoundStore<import("zustand").StoreApi<FormStoreState>>;
26
- export declare const useFormListeners: import("zustand").UseBoundStore<import("zustand").StoreApi<any>>;
27
- export declare const useFormCleanUp: import("zustand").UseBoundStore<import("zustand").StoreApi<any>>;
48
+ export declare const useFormListeners: import("zustand").UseBoundStore<import("zustand").StoreApi<FormStoreState>>;
49
+ export declare const useFormCleanUp: import("zustand").UseBoundStore<import("zustand").StoreApi<FormStoreState>>;
28
50
  export {};
@@ -3,12 +3,12 @@ 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
- const useFormStore = create((storeSet, storeGet) => ({
6
+ const createFormStoreSlice = (storeSet, storeGet, api) => ({
7
7
  forms: {},
8
8
  initialValues: {},
9
9
  formStates: {},
10
10
  cacheData: {},
11
- formInstances: [],
11
+ formInstances: {},
12
12
  submitHistory: {},
13
13
  setData(formName, name, value) {
14
14
  return storeSet(produce((state) => {
@@ -191,8 +191,8 @@ const useFormStore = create((storeSet, storeGet) => ({
191
191
  }
192
192
  }));
193
193
  }
194
- }));
195
- const useFormListeners = create((storeSet, storeGet) => ({
194
+ });
195
+ const createListenersSlice = (storeSet, storeGet, api) => ({
196
196
  listeners: [],
197
197
  getListener(formItemId) {
198
198
  return storeGet().listeners.find((l) => l.formItemId === formItemId);
@@ -261,8 +261,8 @@ const useFormListeners = create((storeSet, storeGet) => ({
261
261
  }
262
262
  }));
263
263
  }
264
- }));
265
- const useFormCleanUp = create((storeSet) => ({
264
+ });
265
+ const createCleanUpSlice = (storeSet, storeGet, api) => ({
266
266
  cleanUpStack: [],
267
267
  setCleanUpStack({ name, type, itemKey, formName }) {
268
268
  return storeSet(produce((state) => {
@@ -276,7 +276,14 @@ const useFormCleanUp = create((storeSet) => ({
276
276
  state.cleanUpStack = [];
277
277
  }));
278
278
  }
279
+ });
280
+ const useFormStore = create((...a) => ({
281
+ ...createFormStoreSlice(...a),
282
+ ...createListenersSlice(...a),
283
+ ...createCleanUpSlice(...a)
279
284
  }));
285
+ const useFormListeners = useFormStore;
286
+ const useFormCleanUp = useFormStore;
280
287
  export {
281
288
  useFormCleanUp,
282
289
  useFormListeners,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-form-manage",
3
- "version": "1.0.8-beta.10",
3
+ "version": "1.0.8-beta.12",
4
4
  "description": "Lightweight React form management with list and listener support.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,6 +36,7 @@
36
36
  "@emotion/styled": "^11.14.1",
37
37
  "@eslint/js": "^9.14.0",
38
38
  "@mui/material": "^7.3.7",
39
+ "@types/lodash": "^4.17.23",
39
40
  "@types/node": "^22.9.3",
40
41
  "@types/react": "^19.2.9",
41
42
  "@types/react-dom": "^19.2.3",
package/src/App.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { Checkbox } from "@mui/material";
1
+ import { Checkbox, Dialog, DialogContent, TextField } from "@mui/material";
2
2
  import { Button, Input } from "antd";
3
3
  import { motion } from "framer-motion";
4
4
  import { useEffect } from "react";
@@ -8,10 +8,45 @@ import InputWrapper from "./components/Form/InputWrapper";
8
8
  import Form, { useForm } from "./providers/Form";
9
9
 
10
10
  import { Form as AntdForm } from "antd";
11
+ import { useToggle } from "minh-custom-hooks-release";
12
+
13
+ function TestFormWatch() {
14
+ const watchValue = Form.useWatch("numericCode");
15
+
16
+ return <div>TestFormWatch</div>;
17
+ }
18
+
19
+ function TestMuiDialog({
20
+ open,
21
+ onClose,
22
+ }: {
23
+ open: boolean;
24
+ onClose: () => void;
25
+ }) {
26
+ return (
27
+ <Dialog open={open} onClose={onClose}>
28
+ <DialogContent>
29
+ {" "}
30
+ <Form
31
+ onFinish={(values) => {
32
+ console.log("Mui Dialog Form Values: ", values);
33
+ }}
34
+ formName="ADD_FORM"
35
+ >
36
+ <FormItem name="oooo">
37
+ <TextField />
38
+ </FormItem>
39
+
40
+ <Button htmlType="submit">Submit</Button>
41
+ </Form>
42
+ </DialogContent>
43
+ </Dialog>
44
+ );
45
+ }
11
46
 
12
47
  const App = () => {
13
48
  const [form] = useForm("form1");
14
-
49
+ const { state: open, toggle } = useToggle(false);
15
50
  const watchCheckBox = Form.useWatch("checkControlledAfterInit", "form1");
16
51
 
17
52
  useEffect(() => {
@@ -155,7 +190,7 @@ const App = () => {
155
190
  <Checkbox />
156
191
  </FormItem>
157
192
  </motion.div>
158
-
193
+ <TestFormWatch />
159
194
  <Button
160
195
  onClick={() => {
161
196
  const current = form?.getFieldValue("checkControlledAfterInit");
@@ -174,6 +209,9 @@ const App = () => {
174
209
  Reset
175
210
  </Button>
176
211
  </Form>
212
+
213
+ <Button onClick={toggle}>Toggle Dialog</Button>
214
+ <TestMuiDialog onClose={toggle} open={open} />
177
215
  </div>
178
216
  );
179
217
  };
@@ -1,20 +1,16 @@
1
1
  import { useEffect } from "react";
2
2
  import { useShallow } from "zustand/react/shallow";
3
- import {
4
- useFormCleanUp,
5
- useFormListeners,
6
- useFormStore,
7
- } from "../../stores/formStore";
3
+ import { useFormStore } from "../../stores/formStore";
8
4
 
9
5
  const FormCleanUp = () => {
10
- const { cleanUpStack, clearCleanUpStack } = useFormCleanUp(
6
+ const { cleanUpStack, clearCleanUpStack } = useFormStore(
11
7
  useShallow((state) => ({
12
8
  cleanUpStack: state.cleanUpStack,
13
9
  clearCleanUpStack: state.clearCleanUpStack,
14
10
  })),
15
11
  );
16
12
 
17
- const { revokeListener } = useFormListeners(
13
+ const { revokeListener } = useFormStore(
18
14
  useShallow((state) => ({
19
15
  revokeListener: state.revokeListener,
20
16
  })),
@@ -1,4 +1,4 @@
1
- import useTestRrenderListControl from "../../hooks/useFormListControl";
1
+ import useFormListControl from "../../hooks/useFormListControl";
2
2
  import type { FormInstance } from "../../stores/formStore";
3
3
 
4
4
  export interface FormListProps<T = any> {
@@ -6,11 +6,24 @@ export interface FormListProps<T = any> {
6
6
  initialValues?: T[];
7
7
  form?: FormInstance;
8
8
  formName?: string;
9
- children: (fields: Array<{ name: string; key: string }>, operations: { add: (index: number) => void; remove: (opts: { index?: number; key?: string }) => void; move: (opts: { from?: number; fromKey?: string; to: number }) => void }) => React.ReactNode;
9
+ children: (
10
+ fields: Array<{ name: string; key: string }>,
11
+ operations: {
12
+ add: (index: number) => void;
13
+ remove: (opts: { index?: number; key?: string }) => void;
14
+ move: (opts: { from?: number; fromKey?: string; to: number }) => void;
15
+ },
16
+ ) => React.ReactNode;
10
17
  }
11
18
 
12
- const FormList = <T = any>({ name, initialValues, form, formName, children }: FormListProps<T>) => {
13
- const { listFields, ...actions } = useTestRrenderListControl({
19
+ const FormList = <T = any,>({
20
+ name,
21
+ initialValues,
22
+ form,
23
+ formName,
24
+ children,
25
+ }: FormListProps<T>) => {
26
+ const { listFields, ...actions } = useFormListControl({
14
27
  name,
15
28
  initialValues,
16
29
  form,
@@ -18,11 +18,7 @@ import {
18
18
  IS_VIETNAMESE_PHONE_NUMBER_REGEX,
19
19
  } from "../constants/validation";
20
20
  import { useFormContext } from "../providers/Form";
21
- import {
22
- useFormCleanUp,
23
- useFormListeners,
24
- useFormStore,
25
- } from "../stores/formStore";
21
+ import { useFormStore } from "../stores/formStore";
26
22
 
27
23
  import type { FormInstance } from "../stores/formStore";
28
24
  import type {
@@ -98,7 +94,7 @@ export default function useFormItemControl<T = any>({
98
94
  }),
99
95
  );
100
96
 
101
- const { setCleanUpStack } = useFormCleanUp(
97
+ const { setCleanUpStack } = useFormStore(
102
98
  useShallow((state) => ({
103
99
  setCleanUpStack: state.setCleanUpStack,
104
100
  })),
@@ -130,7 +126,7 @@ export default function useFormItemControl<T = any>({
130
126
  }),
131
127
  );
132
128
 
133
- const { listener, setListener } = useFormListeners(
129
+ const { listener, setListener } = useFormStore(
134
130
  useShallow((state) => {
135
131
  // console.log(
136
132
  // "Get listener from store: ",
@@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
3
3
  import { v4 } from "uuid";
4
4
  import { useShallow } from "zustand/react/shallow";
5
5
  import { useFormContext } from "../providers/Form";
6
- import { useFormCleanUp, useFormStore } from "../stores/formStore";
6
+ import { useFormStore } from "../stores/formStore";
7
7
 
8
8
  import type { FormInstance } from "../stores/formStore";
9
9
 
@@ -19,7 +19,7 @@ interface UseFormListControlProps {
19
19
  interface UseFormListControlReturn {
20
20
  listFields: ListField[];
21
21
  move: (opts: { from?: number; fromKey?: string; to: number }) => void;
22
- add: (index: number) => void;
22
+ add: (index?: number) => void;
23
23
  remove: (opts: { index?: number; key?: string }) => void;
24
24
  }
25
25
 
@@ -31,7 +31,9 @@ export default function useFormListControl<T = any>({
31
31
  }: UseFormListControlProps): UseFormListControlReturn {
32
32
  const contextForm = useFormContext();
33
33
  const getFormValues = useFormStore((state) => state.getFormValues);
34
- const [listFormInitValues, setListFormInitValues] = useState<any[] | undefined>(undefined);
34
+ const [listFormInitValues, setListFormInitValues] = useState<
35
+ any[] | undefined
36
+ >(undefined);
35
37
  const { clearCacheData, setCacheData } = useFormStore(
36
38
  useShallow((state) => ({
37
39
  cacheData: state.cacheData,
@@ -39,7 +41,7 @@ export default function useFormListControl<T = any>({
39
41
  setCacheData: state.setCacheData,
40
42
  })),
41
43
  );
42
- const { setCleanUpStack } = useFormCleanUp(
44
+ const { setCleanUpStack } = useFormStore(
43
45
  useShallow((state) => ({
44
46
  setCleanUpStack: state.setCleanUpStack,
45
47
  })),
@@ -135,13 +137,9 @@ export default function useFormListControl<T = any>({
135
137
  );
136
138
  };
137
139
 
138
- const add = (index: number) => {
140
+ const add = (index?: number) => {
139
141
  setListFields((prev) => {
140
- if (index > prev.length) return prev;
141
-
142
- if (index < 0) return prev;
143
-
144
- if (index === prev.length) {
142
+ if (isNil(index) || index === prev.length) {
145
143
  const newName = `${name}.${prev.length}`;
146
144
  const newKey = v4();
147
145
  const result = [
@@ -154,6 +152,9 @@ export default function useFormListControl<T = any>({
154
152
 
155
153
  return result;
156
154
  }
155
+ if (index > prev.length) return prev;
156
+
157
+ if (index < 0) return prev;
157
158
 
158
159
  const clonePrev = [...prev];
159
160
 
@@ -217,7 +218,15 @@ export default function useFormListControl<T = any>({
217
218
  });
218
219
  };
219
220
 
220
- const move = ({ from, fromKey, to }: { from?: number; fromKey?: string; to: number }) => {
221
+ const move = ({
222
+ from,
223
+ fromKey,
224
+ to,
225
+ }: {
226
+ from?: number;
227
+ fromKey?: string;
228
+ to: number;
229
+ }) => {
221
230
  setListFields((prev) => {
222
231
  console.log("move list item: ", { from, to });
223
232
  if (
@@ -286,7 +295,7 @@ export default function useFormListControl<T = any>({
286
295
  useEffect(() => {
287
296
  console.log("Trigger init", formState?.isInitied, internalInitValue);
288
297
  if (formState?.isInitied) {
289
- if (Array.isArray(listFormInitValues)) {
298
+ if (Array.isArray(listFormInitValues)) {
290
299
  const result = listFormInitValues?.map((_, i) => {
291
300
  const itemName = `${name}.${i}`;
292
301
  const key = v4();
package/src/index.ts CHANGED
@@ -19,6 +19,13 @@ import InputWrapper, { type InputWrapperProps } from "./components/Form/InputWra
19
19
  import useFormItemControl from "./hooks/useFormItemControl";
20
20
  import useFormListControl from "./hooks/useFormListControl";
21
21
 
22
+ import {
23
+ useFormStore,
24
+ type FormInstance,
25
+ type ListenerItem,
26
+ type CleanUpItem
27
+ } from "./stores/formStore";
28
+
22
29
  export {
23
30
  Form,
24
31
  FormItem,
@@ -31,6 +38,7 @@ export {
31
38
  useWatch,
32
39
  useSubmitDataWatch,
33
40
  useFormStateWatch,
41
+ useFormStore,
34
42
  type FormProps,
35
43
  type FormItemProps,
36
44
  type FormListProps,
@@ -39,6 +47,9 @@ export {
39
47
  type FormFieldError,
40
48
  type SubmitState,
41
49
  type UseFormItemStateWatchReturn,
50
+ type FormInstance,
51
+ type ListenerItem,
52
+ type CleanUpItem,
42
53
  SUBMIT_STATE,
43
54
  };
44
55
 
@@ -1,4 +1,4 @@
1
- import { cloneDeep, get, isEqual, last, set, uniq } from "lodash";
1
+ import { cloneDeep, get, isEqual, isNil, last, set, uniqBy } from "lodash";
2
2
  import { useTask } from "minh-custom-hooks-release";
3
3
  import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
4
4
  import { createContext, useContext, useEffect, useState } from "react";
@@ -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 { useFormListeners, useFormStore } from "../stores/formStore";
9
+ import { useFormStore } from "../stores/formStore";
10
10
  import type {
11
11
  PublicFormInstance,
12
12
  UseFormItemStateWatchReturn,
@@ -81,7 +81,7 @@ export default function Form<T = any>({
81
81
  })),
82
82
  );
83
83
 
84
- const { getListeners } = useFormListeners(
84
+ const { getListeners } = useFormStore(
85
85
  useShallow((state) => {
86
86
  return {
87
87
  getListeners: state.getListeners,
@@ -174,8 +174,8 @@ export default function Form<T = any>({
174
174
 
175
175
  const resultValues = cloneDeep(formValues);
176
176
  const cleanValues = {} as T;
177
- uniq(listeners, (l) => l.name).forEach((l) => {
178
- set(cleanValues, l.name, get(resultValues, l.name));
177
+ uniqBy(listeners, (l: any) => l.name).forEach((l) => {
178
+ set(cleanValues as any, l.name, get(resultValues, l.name));
179
179
  });
180
180
 
181
181
  const handleIsolateCase = async () => {
@@ -413,14 +413,18 @@ export function useFormContext() {
413
413
  export function useForm<T = any>(
414
414
  formNameOrFormInstance?: string | PublicFormInstance<T>,
415
415
  ) {
416
- const targetFormName =
417
- typeof formNameOrFormInstance === "object" &&
418
- formNameOrFormInstance !== null
416
+ const formContext = useContext(FormContext);
417
+ const targetFormName = isNil(formNameOrFormInstance)
418
+ ? formContext?.formName
419
+ : typeof formNameOrFormInstance === "object" &&
420
+ formNameOrFormInstance !== null
419
421
  ? (formNameOrFormInstance as PublicFormInstance<T>).formName
420
422
  : (formNameOrFormInstance as string | undefined);
421
423
 
422
424
  const formInstance = useFormStore((state) => {
423
- return state.formInstances.find((i) => i.formName === targetFormName);
425
+ return Object.values(state.formInstances).find(
426
+ (i) => i.formName === targetFormName,
427
+ );
424
428
  }) as PublicFormInstance<T> | undefined;
425
429
 
426
430
  return [formInstance];
@@ -507,8 +511,30 @@ export const useFormItemStateWatch = <T = any,>(
507
511
  };
508
512
  };
509
513
 
514
+ export const useWatchFormListeners = <T = any,>(
515
+ formNameOrFormInstance?: string | PublicFormInstance<T>,
516
+ ) => {
517
+ const [formInstance] = useForm<T>(formNameOrFormInstance as any);
518
+
519
+ const listeners = useFormStore(
520
+ useShallow((state) => {
521
+ const allListeners = state.listeners;
522
+ return {
523
+ listeners: allListeners.filter((l) =>
524
+ !isNil(formNameOrFormInstance)
525
+ ? true
526
+ : l.formName === formInstance?.formName,
527
+ ),
528
+ };
529
+ }),
530
+ );
531
+
532
+ return listeners;
533
+ };
534
+
510
535
  Form.useForm = useForm;
511
536
  Form.useWatch = useWatch;
512
537
  Form.useSubmitDataWatch = useSubmitDataWatch;
513
538
  Form.useFormStateWatch = useFormStateWatch;
514
539
  Form.useFormItemStateWatch = useFormItemStateWatch;
540
+ Form.useWatchFormListeners = useWatchFormListeners;
@@ -17,7 +17,7 @@ export interface FormInstance {
17
17
  setFieldFocus: (name: string) => void;
18
18
  }
19
19
 
20
- interface ListenerItem {
20
+ export interface ListenerItem {
21
21
  name?: string;
22
22
  formName?: string;
23
23
  isTouched?: boolean;
@@ -28,9 +28,10 @@ interface ListenerItem {
28
28
  onReset?: any;
29
29
  onFocus?: any;
30
30
  emitFocus?: any;
31
+ isInitied?: boolean;
31
32
  }
32
33
 
33
- interface CleanUpItem {
34
+ export interface CleanUpItem {
34
35
  name?: string;
35
36
  type?: string;
36
37
  key: string;
@@ -43,19 +44,23 @@ interface FormStoreState {
43
44
  initialValues: Record<string, any>;
44
45
  formStates: Record<string, any>;
45
46
  cacheData: Record<string, any>;
46
- formInstances: FormInstance[];
47
+ formInstances: Record<string, FormInstance>;
47
48
  submitHistory: Record<string, any[]>;
49
+ listeners: ListenerItem[];
50
+ cleanUpStack: CleanUpItem[];
48
51
  // methods (loose typed for incremental migration)
49
52
  [key: string]: any;
50
53
  }
51
- export const useFormStore = create<FormStoreState>(
52
- (storeSet: any, storeGet: any) => ({
53
- forms: {},
54
- initialValues: {},
55
- formStates: {},
56
- cacheData: {},
57
- formInstances: [],
58
- submitHistory: {},
54
+
55
+ // Combined store containing all form-related state
56
+ // Form Store Slice
57
+ const createFormStoreSlice = (storeSet: any, storeGet: any, api: any) => ({
58
+ forms: {},
59
+ initialValues: {},
60
+ formStates: {},
61
+ cacheData: {},
62
+ formInstances: {},
63
+ submitHistory: {},
59
64
  setData(formName, name, value) {
60
65
  return storeSet(
61
66
  produce<any>((state: any) => {
@@ -353,13 +358,13 @@ export const useFormStore = create<FormStoreState>(
353
358
  }),
354
359
  );
355
360
  },
356
- }),
357
- );
361
+ });
358
362
 
359
- export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
363
+ // Listeners Slice
364
+ const createListenersSlice = (storeSet: any, storeGet: any, api: any) => ({
360
365
  listeners: [],
361
- getListener(formItemId) {
362
- return storeGet().listeners.find((l) => l.formItemId === formItemId);
366
+ getListener(formItemId: string) {
367
+ return storeGet().listeners.find((l: any) => l.formItemId === formItemId);
363
368
  },
364
369
  getListeners() {
365
370
  return storeGet().listeners;
@@ -376,7 +381,7 @@ export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
376
381
  onFocus,
377
382
  emitFocus,
378
383
  isInitied,
379
- }) {
384
+ }: any) {
380
385
  return storeSet(
381
386
  produce<any>((state: any) => {
382
387
  const storeListeners = state.listeners;
@@ -433,7 +438,7 @@ export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
433
438
  }),
434
439
  );
435
440
  },
436
- revokeListener(formItemId, onAfterRevoke) {
441
+ revokeListener(formItemId: string, onAfterRevoke: any) {
437
442
  return storeSet(
438
443
  produce<any>((state: any) => {
439
444
  const storeListeners = state.listeners;
@@ -442,8 +447,6 @@ export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
442
447
  (l: any) => l.formItemId == formItemId,
443
448
  );
444
449
 
445
- // console.log("Find item for revoke: ", findListenerIndex, formItemId);
446
-
447
450
  if (findListenerIndex > -1) {
448
451
  const listenersInfo = cloneDeep(storeListeners[findListenerIndex]);
449
452
 
@@ -462,11 +465,12 @@ export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
462
465
  }),
463
466
  );
464
467
  },
465
- }));
468
+ });
466
469
 
467
- export const useFormCleanUp = create<any>((storeSet: any) => ({
470
+ // CleanUp Slice
471
+ const createCleanUpSlice = (storeSet: any, storeGet: any, api: any) => ({
468
472
  cleanUpStack: [],
469
- setCleanUpStack({ name, type, itemKey, formName }) {
473
+ setCleanUpStack({ name, type, itemKey, formName }: any) {
470
474
  return storeSet(
471
475
  produce<any>((state: any) => {
472
476
  const oldValues = state.cleanUpStack;
@@ -482,4 +486,15 @@ export const useFormCleanUp = create<any>((storeSet: any) => ({
482
486
  }),
483
487
  );
484
488
  },
489
+ });
490
+
491
+ // Combined Store
492
+ export const useFormStore = create<FormStoreState>((...a) => ({
493
+ ...createFormStoreSlice(...a),
494
+ ...createListenersSlice(...a),
495
+ ...createCleanUpSlice(...a),
485
496
  }));
497
+
498
+ // Deprecated exports - use useFormStore instead
499
+ export const useFormListeners = useFormStore;
500
+ export const useFormCleanUp = useFormStore;