react-form-manage 1.0.8-beta.1 → 1.0.8-beta.11

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
@@ -1,6 +1,25 @@
1
1
  # Changelog
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.8-beta.3] - 2026-01-22
6
+
7
+ ## [1.0.8-beta.7] - 2026-01-24
8
+ - Add control flag for FormItem to support rendering MUI uncontrolled components when initial value is undefined (control on init).
9
+ - Fix: `onReset` did not restore listener state to init.
10
+ - Fix: reset did not return Form submit state to `idle`.
11
+ - Add `hidden` prop to allow hiding components while still assigning a value.
12
+
13
+ Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
14
+ - Add `UseFormItemStateWatchReturn` type export for `useFormItemStateWatch` hook
15
+
16
+ - Add isTouched field to FormItem for tracking user interaction state
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
+
4
23
  ## [1.0.8-beta.1] - 2026-01-22
5
24
 
6
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,7 +1,7 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { ValidationRule } from "../../types/public";
3
3
  export interface FormItemProps {
4
- children: ReactElement;
4
+ children: ReactElement<any>;
5
5
  name: string;
6
6
  formName?: string;
7
7
  initialValue?: any;
@@ -9,5 +9,7 @@ export interface FormItemProps {
9
9
  rules?: ValidationRule[];
10
10
  valuePropName?: string;
11
11
  getValueFromEvent?: (...args: any[]) => any;
12
+ controlAfterInit?: boolean;
13
+ hidden?: boolean;
12
14
  }
13
- export default function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName, getValueFromEvent, }: FormItemProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
15
+ export default function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName, getValueFromEvent, controlAfterInit, hidden, }: FormItemProps): import("react/jsx-runtime").JSX.Element;
@@ -1,10 +1,11 @@
1
- import { cloneElement, useRef, useState } from "react";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cloneElement, Fragment, useRef, useState } from "react";
2
3
  import { v4 } from "uuid";
3
4
  import useFormItemControl from "../../hooks/useFormItemControl";
4
- function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName = "value", getValueFromEvent }) {
5
+ function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName = "value", getValueFromEvent, controlAfterInit = false, hidden }) {
5
6
  const elRef = useRef(null);
6
7
  const [formItemId] = useState(externalFormItemId != null ? externalFormItemId : v4());
7
- const { value, onChange, errors, state, onFocus, isDirty, submitState } = useFormItemControl({
8
+ const { value, onChange, errors, state, onFocus, isDirty, submitState, isTouched, isInitied } = useFormItemControl({
8
9
  formName,
9
10
  name,
10
11
  initialValue,
@@ -12,7 +13,8 @@ function FormItem({ children, name, formName, initialValue, formItemId: external
12
13
  rules,
13
14
  elementRef: elRef
14
15
  });
15
- return cloneElement(children, {
16
+ return _jsx(Fragment, { children: !hidden && children ? cloneElement(children, {
17
+ name,
16
18
  // ref: inputRef,
17
19
  [valuePropName]: value,
18
20
  onChange: (...args) => {
@@ -38,8 +40,9 @@ function FormItem({ children, name, formName, initialValue, formItemId: external
38
40
  onFocus,
39
41
  validateState: state,
40
42
  ref: elRef,
41
- submitState
42
- });
43
+ submitState,
44
+ isTouched
45
+ }) : null }, `control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${formItemId}`);
43
46
  }
44
47
  export {
45
48
  FormItem as default
@@ -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,
@@ -18,7 +18,9 @@ export interface UseFormItemControlReturn {
18
18
  errors: FormFieldError[];
19
19
  onFocus: () => void;
20
20
  isDirty?: boolean;
21
+ isTouched?: boolean;
21
22
  submitState?: SubmitState;
23
+ isInitied?: boolean;
22
24
  }
23
25
  export default function useFormItemControl<T = any>({ formName, form, name, initialValue, formItemId, rules, elementRef, }: UseFormItemControlProps): UseFormItemControlReturn;
24
26
  export {};
@@ -38,7 +38,7 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
38
38
  }));
39
39
  const onInitData = (value2) => {
40
40
  setInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name, value2);
41
- onChange(value2, { notTriggerDirty: true });
41
+ onChange(value2, { notTriggerDirty: true, initiedData: true });
42
42
  };
43
43
  const onFocus = () => {
44
44
  setListener({
@@ -60,10 +60,21 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
60
60
  isTouched: listener == null ? void 0 : listener.isTouched
61
61
  });
62
62
  }
63
+ if ((options == null ? void 0 : options.initiedData) === true) {
64
+ setListener({
65
+ formItemId,
66
+ isInitied: true
67
+ });
68
+ }
63
69
  setData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name, value2);
64
70
  };
65
71
  const onReset = (value2) => {
66
- onChange(isNil(value2) ? getInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name) : value2);
72
+ setListener({
73
+ formItemId,
74
+ isDirty: false,
75
+ isTouched: false
76
+ });
77
+ onChange(isNil(value2) ? getInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name) : value2, { notTriggerDirty: true, initiedData: true });
67
78
  };
68
79
  const internalRules = useMemo(() => {
69
80
  return rules || [];
@@ -262,7 +273,10 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
262
273
  onInitData(initialValue);
263
274
  }
264
275
  } else {
265
- onChange(internalInitValue, { notTriggerDirty: true });
276
+ onChange(internalInitValue, {
277
+ notTriggerDirty: true,
278
+ initiedData: true
279
+ });
266
280
  }
267
281
  }
268
282
  return;
@@ -319,7 +333,9 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
319
333
  errors,
320
334
  onFocus,
321
335
  isDirty: listener == null ? void 0 : listener.isDirty,
322
- submitState
336
+ isTouched: listener == null ? void 0 : listener.isTouched,
337
+ submitState,
338
+ isInitied: listener == null ? void 0 : listener.isInitied
323
339
  };
324
340
  }
325
341
  export {
@@ -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;
@@ -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
@@ -1,4 +1,4 @@
1
- import Form, { useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps, type ValidationRule, type FormFieldError, type SubmitState } from "./providers/Form";
1
+ import Form, { useForm, useWatch, useSubmitDataWatch, useFormStateWatch, type FormProps, type ValidationRule, type FormFieldError, type SubmitState, type UseFormItemStateWatchReturn } from "./providers/Form";
2
2
  import { SUBMIT_STATE } from "./constants/form";
3
3
  import FormItem, { type FormItemProps } from "./components/Form/FormItem";
4
4
  import FormList, { type FormListProps } from "./components/Form/FormList";
@@ -6,5 +6,5 @@ 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, SUBMIT_STATE, };
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, };
10
10
  export default Form;
@@ -1,6 +1,6 @@
1
1
  import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
2
- import type { PublicFormInstance } from "../types/public";
3
- export type { ValidationRule, FormFieldError, SubmitState } from "../types/public";
2
+ import type { PublicFormInstance, UseFormItemStateWatchReturn } from "../types/public";
3
+ export type { FormFieldError, SubmitState, UseFormItemStateWatchReturn, ValidationRule, } from "../types/public";
4
4
  export declare const FormContext: import("react").Context<any>;
5
5
  export interface FormProps<T = any> extends Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
6
6
  children: ReactNode;
@@ -21,6 +21,7 @@ declare namespace Form {
21
21
  var useWatch: typeof import("./Form").useWatch;
22
22
  var useSubmitDataWatch: typeof import("./Form").useSubmitDataWatch;
23
23
  var useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
24
+ var useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
24
25
  }
25
26
  export default Form;
26
27
  export declare function useFormContext(): any;
@@ -32,3 +33,4 @@ export declare function useSubmitDataWatch<T = any>({ formNameOrFormInstance, tr
32
33
  mapFn?: (v: any, prev: any) => any;
33
34
  }): T | undefined;
34
35
  export declare const useFormStateWatch: <T = any>(formNameOrFormInstance?: string | PublicFormInstance<T>) => any;
36
+ export declare const useFormItemStateWatch: <T = any>(nameOrFormItemId: string, formNameOrFormInstance?: string | PublicFormInstance<T>) => UseFormItemStateWatchReturn;
@@ -1,33 +1,44 @@
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
+ import { SUBMIT_STATE } from "../constants/form";
8
9
  import { useFormListeners, useFormStore } from "../stores/formStore";
9
10
  import { getAllNoneObjStringPath } from "../utils/obj.util";
10
11
  const FormContext = createContext(null);
11
12
  function Form({ children, formName, initialValues, onFinish, onReject, onFinally, FormElement, ...props }) {
12
- const { appInitValue, getFormItemValue, setInitData, setData, getFormValues, setFormState, setFormInstance, revokeFormInstance, setSubmitHistory, clearFormValues, clearFormInitialValues, clearFormState } = useFormStore(useShallow((state) => {
13
- var _a;
14
- return {
15
- appInitValue: state.initialValues,
16
- setInitData: state.setInitData,
17
- getFormValues: state.getFormValues,
18
- setFormState: state.setFormState,
19
- setFormInstance: state.setFormInstance,
20
- revokeFormInstance: state.revokeFormInstance,
21
- setData: state.setData,
22
- setSubmitHistory: state.setSubmitHistory,
23
- getFormItemValue: state.getFormItemValue,
24
- clearFormValues: state.clearFormValues,
25
- clearFormInitialValues: state.clearFormInitialValues,
26
- clearFormState: state.clearFormState,
27
- // Test, nhớ xóa sau khi xong
28
- formStates: (_a = state.formStates) == null ? void 0 : _a[formName]
29
- };
30
- }));
13
+ const {
14
+ // appInitValue,
15
+ getFormItemValue,
16
+ setInitData,
17
+ setData,
18
+ getFormValues,
19
+ setFormState,
20
+ setFormInstance,
21
+ revokeFormInstance,
22
+ setSubmitHistory,
23
+ clearFormValues,
24
+ clearFormInitialValues,
25
+ clearFormState
26
+ } = useFormStore(useShallow((state) => ({
27
+ // appInitValue: state.initialValues,
28
+ setInitData: state.setInitData,
29
+ getFormValues: state.getFormValues,
30
+ setFormState: state.setFormState,
31
+ setFormInstance: state.setFormInstance,
32
+ revokeFormInstance: state.revokeFormInstance,
33
+ setData: state.setData,
34
+ setSubmitHistory: state.setSubmitHistory,
35
+ getFormItemValue: state.getFormItemValue,
36
+ clearFormValues: state.clearFormValues,
37
+ clearFormInitialValues: state.clearFormInitialValues,
38
+ clearFormState: state.clearFormState
39
+ // Test, nhớ xóa sau khi xong
40
+ // formStates: state.formStates?.[formName],
41
+ })));
31
42
  const { getListeners } = useFormListeners(useShallow((state) => {
32
43
  return {
33
44
  getListeners: state.getListeners
@@ -95,7 +106,7 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
95
106
  const formValues = getFormValues(formName);
96
107
  const resultValues = cloneDeep(formValues);
97
108
  const cleanValues = {};
98
- uniq(listeners, (l) => l.name).forEach((l) => {
109
+ uniqBy(listeners, (l) => l.name).forEach((l) => {
99
110
  set(cleanValues, l.name, get(resultValues, l.name));
100
111
  });
101
112
  const handleIsolateCase = async () => {
@@ -167,7 +178,8 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
167
178
  reset();
168
179
  flushSync(setFormState({
169
180
  formName,
170
- isInitied: false
181
+ isInitied: false,
182
+ submitState: SUBMIT_STATE.IDLE
171
183
  }));
172
184
  const totalListenerFields = getListeners();
173
185
  if (Array.isArray(resetOptions)) {
@@ -252,7 +264,8 @@ function useFormContext() {
252
264
  return c;
253
265
  }
254
266
  function useForm(formNameOrFormInstance) {
255
- 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;
256
269
  const formInstance = useFormStore((state) => {
257
270
  return state.formInstances.find((i) => i.formName === targetFormName);
258
271
  });
@@ -287,15 +300,28 @@ const useFormStateWatch = (formNameOrFormInstance) => {
287
300
  });
288
301
  return formState;
289
302
  };
303
+ const useFormItemStateWatch = (nameOrFormItemId, formNameOrFormInstance) => {
304
+ const [formInstance] = useForm(formNameOrFormInstance);
305
+ const listener = useFormStore((state) => {
306
+ return state.getListeners().find((l) => l.formName === (formInstance == null ? void 0 : formInstance.formName) && (l.name === nameOrFormItemId || l.formItemId === nameOrFormItemId));
307
+ });
308
+ return {
309
+ isTouched: listener == null ? void 0 : listener.isTouched,
310
+ isDirty: listener == null ? void 0 : listener.isDirty,
311
+ errors: (listener == null ? void 0 : listener.internalErrors) || []
312
+ };
313
+ };
290
314
  Form.useForm = useForm;
291
315
  Form.useWatch = useWatch;
292
316
  Form.useSubmitDataWatch = useSubmitDataWatch;
293
317
  Form.useFormStateWatch = useFormStateWatch;
318
+ Form.useFormItemStateWatch = useFormItemStateWatch;
294
319
  export {
295
320
  FormContext,
296
321
  Form as default,
297
322
  useForm,
298
323
  useFormContext,
324
+ useFormItemStateWatch,
299
325
  useFormStateWatch,
300
326
  useSubmitDataWatch,
301
327
  useWatch
@@ -200,7 +200,7 @@ const useFormListeners = create((storeSet, storeGet) => ({
200
200
  getListeners() {
201
201
  return storeGet().listeners;
202
202
  },
203
- setListener({ formName, name, onChange, onReset, isTouched, isDirty, formItemId, internalErrors, onFocus, emitFocus }) {
203
+ setListener({ formName, name, onChange, onReset, isTouched, isDirty, formItemId, internalErrors, onFocus, emitFocus, isInitied }) {
204
204
  return storeSet(produce((state) => {
205
205
  const storeListeners = state.listeners;
206
206
  const findListenerIndex = state.listeners.findIndex((l) => l.formItemId === formItemId);
@@ -232,17 +232,21 @@ const useFormListeners = create((storeSet, storeGet) => ({
232
232
  if (!isNil(emitFocus)) {
233
233
  storeListeners[findListenerIndex].emitFocus = emitFocus;
234
234
  }
235
+ if (!isNil(isInitied)) {
236
+ storeListeners[findListenerIndex].isInitied = isInitied;
237
+ }
235
238
  return;
236
239
  }
237
240
  storeListeners.push({
238
241
  name,
239
242
  formName,
240
- isTouched,
241
- isDirty,
243
+ isTouched: Boolean(isTouched),
244
+ isDirty: Boolean(isDirty),
242
245
  formItemId,
243
246
  internalErrors,
244
247
  onChange,
245
- onReset
248
+ onReset,
249
+ isInitied: Boolean(isInitied)
246
250
  });
247
251
  }));
248
252
  },
@@ -61,6 +61,11 @@ export interface UseFormItemReturn<T = any> {
61
61
  isDirty?: boolean;
62
62
  submitState?: SubmitState;
63
63
  }
64
+ export interface UseFormItemStateWatchReturn {
65
+ isTouched?: boolean;
66
+ isDirty?: boolean;
67
+ errors: FormFieldError[];
68
+ }
64
69
  export interface UseFormListProps<T = any> {
65
70
  name?: string;
66
71
  form?: PublicFormInstance<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-form-manage",
3
- "version": "1.0.8-beta.1",
3
+ "version": "1.0.8-beta.11",
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",
@@ -45,6 +46,7 @@
45
46
  "eslint": "^9.14.0",
46
47
  "eslint-plugin-react-hooks": "^5.1.0",
47
48
  "eslint-plugin-react-refresh": "^0.4.14",
49
+ "framer-motion": "^12.29.0",
48
50
  "globals": "^15.12.0",
49
51
  "react": "^19.0.0",
50
52
  "react-dom": "^19.0.0",
package/src/App.tsx CHANGED
@@ -1,13 +1,25 @@
1
+ import { Checkbox } from "@mui/material";
1
2
  import { Button, Input } from "antd";
3
+ import { motion } from "framer-motion";
2
4
  import { useEffect } from "react";
3
5
  import FormItem from "./components/Form/FormItem";
4
6
  import FormList from "./components/Form/FormList";
5
7
  import InputWrapper from "./components/Form/InputWrapper";
6
8
  import Form, { useForm } from "./providers/Form";
7
9
 
10
+ import { Form as AntdForm } from "antd";
11
+
12
+ function TestFormWatch() {
13
+ const watchValue = Form.useWatch("numericCode");
14
+
15
+ return <div>TestFormWatch</div>;
16
+ }
17
+
8
18
  const App = () => {
9
19
  const [form] = useForm("form1");
10
20
 
21
+ const watchCheckBox = Form.useWatch("checkControlledAfterInit", "form1");
22
+
11
23
  useEffect(() => {
12
24
  if (form) {
13
25
  // setTimeout(() => {
@@ -17,6 +29,20 @@ const App = () => {
17
29
  }, [form]);
18
30
  return (
19
31
  <div>
32
+ <AntdForm>
33
+ <motion.div
34
+ initial={{ opacity: 0 }}
35
+ animate={{ opacity: 1 }}
36
+ exit={{ opacity: 0 }}
37
+ transition={{ duration: 1 }}
38
+ >
39
+ <AntdForm.Item name={"234"} initialValue={"23432"} label="Antd Input">
40
+ <Input />
41
+ </AntdForm.Item>
42
+ </motion.div>
43
+ </AntdForm>
44
+
45
+ {/* Hidden Test */}
20
46
  <Form
21
47
  initialValues={{
22
48
  TestData: "",
@@ -27,13 +53,25 @@ const App = () => {
27
53
  console.log(values);
28
54
  }}
29
55
  formName={"form1"}
56
+ // hidden
30
57
  >
31
- <FormItem name={"TestData"}>
58
+ <FormItem
59
+ name={"username"}
60
+ rules={[
61
+ {
62
+ required: true,
63
+ message: "Test",
64
+ },
65
+ ]}
66
+ initialValue={"283746"}
67
+ // hidden
68
+ >
32
69
  <InputWrapper>
33
70
  <Input />
34
71
  </InputWrapper>
35
72
  </FormItem>
36
73
 
74
+ {/* Numberic test */}
37
75
  <FormItem
38
76
  name={"numericCode"}
39
77
  rules={[
@@ -48,6 +86,20 @@ const App = () => {
48
86
  <Input placeholder="Mã chỉ gồm số" style={{ width: 200 }} />
49
87
  </InputWrapper>
50
88
  </FormItem>
89
+
90
+ {/* Motion Test */}
91
+ <motion.div
92
+ initial={{ opacity: 0 }}
93
+ animate={{ opacity: 1 }}
94
+ exit={{ opacity: 0 }}
95
+ transition={{ duration: 1 }}
96
+ >
97
+ <FormItem name="motionTest" controlAfterInit initialValue={"1234134"}>
98
+ <InputWrapper>
99
+ <Input placeholder="Motion Test" style={{ width: 200 }} />
100
+ </InputWrapper>
101
+ </FormItem>
102
+ </motion.div>
51
103
  <FormList
52
104
  initialValues={[
53
105
  {
@@ -93,12 +145,40 @@ const App = () => {
93
145
  </div>
94
146
  )}
95
147
  </FormList>
148
+ <motion.div
149
+ initial={{ opacity: 0 }}
150
+ animate={{ opacity: 1 }}
151
+ exit={{ opacity: 0 }}
152
+ transition={{ duration: 1.5 }}
153
+ >
154
+ <FormItem
155
+ valuePropName="checked"
156
+ getValueFromEvent={(_, checked) => checked}
157
+ name="checkControlledAfterInit"
158
+ controlAfterInit={true}
159
+ initialValue={true}
160
+ >
161
+ <Checkbox />
162
+ </FormItem>
163
+ </motion.div>
164
+ <TestFormWatch />
165
+ <Button
166
+ onClick={() => {
167
+ const current = form?.getFieldValue("checkControlledAfterInit");
168
+ console.log("Toggle controlled after init: ", current);
169
+ form?.setFieldValue("checkControlledAfterInit", !current);
170
+ }}
171
+ >
172
+ Toggle
173
+ </Button>
96
174
  <Button htmlType="submit">Submit</Button>
97
175
  <Button
98
176
  onClick={() => {
99
177
  form?.resetFields?.();
100
178
  }}
101
- ></Button>
179
+ >
180
+ Reset
181
+ </Button>
102
182
  </Form>
103
183
  </div>
104
184
  );
@@ -1,11 +1,11 @@
1
1
  import type { ReactElement } from "react";
2
- import { cloneElement, useRef, useState } from "react";
2
+ import { cloneElement, Fragment, useRef, useState } from "react";
3
3
  import { v4 } from "uuid";
4
4
  import useFormItemControl from "../../hooks/useFormItemControl";
5
5
  import type { ValidationRule } from "../../types/public";
6
6
 
7
7
  export interface FormItemProps {
8
- children: ReactElement;
8
+ children: ReactElement<any>;
9
9
  name: string;
10
10
  formName?: string;
11
11
  initialValue?: any;
@@ -13,6 +13,8 @@ export interface FormItemProps {
13
13
  rules?: ValidationRule[];
14
14
  valuePropName?: string;
15
15
  getValueFromEvent?: (...args: any[]) => any;
16
+ controlAfterInit?: boolean;
17
+ hidden?: boolean;
16
18
  }
17
19
 
18
20
  export default function FormItem({
@@ -24,52 +26,85 @@ export default function FormItem({
24
26
  rules,
25
27
  valuePropName = "value",
26
28
  getValueFromEvent,
29
+ controlAfterInit = false,
30
+ hidden,
27
31
  }: FormItemProps) {
28
32
  const elRef = useRef<any>(null);
29
33
 
30
34
  const [formItemId] = useState(externalFormItemId ?? v4());
31
- const { value, onChange, errors, state, onFocus, isDirty, submitState } =
32
- useFormItemControl({
33
- formName,
34
- name,
35
- initialValue,
36
- formItemId,
37
- rules,
38
- elementRef: elRef,
39
- });
35
+ const {
36
+ value,
37
+ onChange,
38
+ errors,
39
+ state,
40
+ onFocus,
41
+ isDirty,
42
+ submitState,
43
+ isTouched,
44
+ isInitied,
45
+ } = useFormItemControl({
46
+ formName,
47
+ name,
48
+ initialValue,
49
+ formItemId,
50
+ rules,
51
+ elementRef: elRef,
52
+ });
40
53
  // console.log("re-render", formName, name);
41
54
 
42
55
  // useEffect(() => {
43
56
  // console.log({ value });
44
57
  // }, [value]);
45
58
 
46
- return cloneElement(children, {
47
- // ref: inputRef,
48
- [valuePropName]: value,
49
- onChange: (...args: any[]) => {
50
- let val = args[0];
51
- if (getValueFromEvent && typeof getValueFromEvent === "function") {
52
- val = getValueFromEvent(...args);
53
- } else {
54
- const e = args[0];
55
- if (e && e.target) {
56
- val = e.target.value;
57
- }
58
- }
59
- onChange(val);
60
- },
61
- // onFocus: () => {
62
- // setIsTouched(true);
63
- // },
64
- // isTouched: isTouched,
65
- isDirty: isDirty,
66
- // errors: errors,
67
- // formState,
59
+ // useEffect(() => {
60
+ // console.log("isInitied changed: ", {
61
+ // isInitied,
62
+ // name,
63
+ // value,
64
+ // controlAfterInit,
65
+ // });
66
+ // }, [isInitied]);
67
+
68
+ return (
69
+ <Fragment
70
+ key={`control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${formItemId}`}
71
+ >
72
+ {!hidden && children
73
+ ? cloneElement(children, {
74
+ name,
75
+ // ref: inputRef,
76
+ [valuePropName]: value,
77
+ onChange: (...args: any[]) => {
78
+ let val = args[0];
79
+ if (
80
+ getValueFromEvent &&
81
+ typeof getValueFromEvent === "function"
82
+ ) {
83
+ val = getValueFromEvent(...args);
84
+ } else {
85
+ const e = args[0];
86
+ if (e && e.target) {
87
+ val = e.target.value;
88
+ }
89
+ }
90
+ onChange(val);
91
+ },
92
+ // onFocus: () => {
93
+ // setIsTouched(true);
94
+ // },
95
+ // isTouched: isTouched,
96
+ isDirty: isDirty,
97
+ // errors: errors,
98
+ // formState,
68
99
 
69
- errors,
70
- onFocus,
71
- validateState: state,
72
- ref: elRef,
73
- submitState,
74
- } as any);
100
+ errors,
101
+ onFocus,
102
+ validateState: state,
103
+ ref: elRef,
104
+ submitState,
105
+ isTouched: isTouched,
106
+ } as any)
107
+ : null}
108
+ </Fragment>
109
+ );
75
110
  }
@@ -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,
@@ -13,6 +13,11 @@ const InputWrapper = ({
13
13
  errors = [],
14
14
  ...props
15
15
  }: InputWrapperProps) => {
16
+ // useEffect(() => {
17
+ // console.log("InputWrapper submitState changed: ", {
18
+ // submitState: props.submitState,
19
+ // });
20
+ // }, [props.submitState]);
16
21
  return (
17
22
  <div>
18
23
  {cloneElement(children, props)}
@@ -50,7 +50,9 @@ export interface UseFormItemControlReturn {
50
50
  errors: FormFieldError[];
51
51
  onFocus: () => void;
52
52
  isDirty?: boolean;
53
+ isTouched?: boolean;
53
54
  submitState?: SubmitState;
55
+ isInitied?: boolean;
54
56
  }
55
57
 
56
58
  const VALID_PREMITIVE_TYPE = ["string", "number", "undefined"];
@@ -153,7 +155,7 @@ export default function useFormItemControl<T = any>({
153
155
  name,
154
156
  value,
155
157
  );
156
- onChange(value, { notTriggerDirty: true });
158
+ onChange(value, { notTriggerDirty: true, initiedData: true });
157
159
  };
158
160
 
159
161
  const onFocus = () => {
@@ -170,7 +172,13 @@ export default function useFormItemControl<T = any>({
170
172
  }
171
173
  };
172
174
 
173
- const onChange = (value: T, options?: any) => {
175
+ const onChange = (
176
+ value: T,
177
+ options?: {
178
+ notTriggerDirty?: boolean;
179
+ initiedData?: boolean;
180
+ },
181
+ ) => {
174
182
  if (options?.notTriggerDirty !== true) {
175
183
  setListener({
176
184
  formItemId,
@@ -178,6 +186,12 @@ export default function useFormItemControl<T = any>({
178
186
  isTouched: listener?.isTouched,
179
187
  });
180
188
  }
189
+ if (options?.initiedData === true) {
190
+ setListener({
191
+ formItemId,
192
+ isInitied: true,
193
+ });
194
+ }
181
195
  setData(formName || form?.formName || contextForm?.formName, name, value);
182
196
  };
183
197
 
@@ -187,10 +201,16 @@ export default function useFormItemControl<T = any>({
187
201
  value,
188
202
  getInitData(formName || form?.formName || contextForm?.formName, name),
189
203
  );
204
+ setListener({
205
+ formItemId,
206
+ isDirty: false,
207
+ isTouched: false,
208
+ });
190
209
  onChange(
191
210
  isNil(value)
192
211
  ? getInitData(formName || form?.formName || contextForm?.formName, name)
193
212
  : value,
213
+ { notTriggerDirty: true, initiedData: true },
194
214
  );
195
215
  };
196
216
 
@@ -521,7 +541,10 @@ export default function useFormItemControl<T = any>({
521
541
  onInitData(initialValue);
522
542
  }
523
543
  } else {
524
- onChange(internalInitValue, { notTriggerDirty: true });
544
+ onChange(internalInitValue, {
545
+ notTriggerDirty: true,
546
+ initiedData: true,
547
+ });
525
548
  }
526
549
  }
527
550
  return;
@@ -596,6 +619,8 @@ export default function useFormItemControl<T = any>({
596
619
  errors,
597
620
  onFocus,
598
621
  isDirty: listener?.isDirty,
622
+ isTouched: listener?.isTouched,
599
623
  submitState,
624
+ isInitied: listener?.isInitied,
600
625
  };
601
626
  }
@@ -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,
@@ -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
@@ -7,6 +7,7 @@ import Form, {
7
7
  type ValidationRule,
8
8
  type FormFieldError,
9
9
  type SubmitState,
10
+ type UseFormItemStateWatchReturn,
10
11
  } from "./providers/Form";
11
12
  import { SUBMIT_STATE } from "./constants/form";
12
13
 
@@ -37,6 +38,7 @@ export {
37
38
  type ValidationRule,
38
39
  type FormFieldError,
39
40
  type SubmitState,
41
+ type UseFormItemStateWatchReturn,
40
42
  SUBMIT_STATE,
41
43
  };
42
44
 
@@ -1,14 +1,23 @@
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";
5
5
  import { flushSync } from "react-dom";
6
6
  import { useShallow } from "zustand/react/shallow"; // Import useShallow
7
7
  import FormCleanUp from "../components/Form/FormCleanUp";
8
+ import { SUBMIT_STATE } from "../constants/form";
8
9
  import { useFormListeners, useFormStore } from "../stores/formStore";
9
- import type { PublicFormInstance } from "../types/public";
10
+ import type {
11
+ PublicFormInstance,
12
+ UseFormItemStateWatchReturn,
13
+ } from "../types/public";
10
14
  import { getAllNoneObjStringPath } from "../utils/obj.util";
11
- export type { ValidationRule, FormFieldError, SubmitState } from "../types/public";
15
+ export type {
16
+ FormFieldError,
17
+ SubmitState,
18
+ UseFormItemStateWatchReturn,
19
+ ValidationRule,
20
+ } from "../types/public";
12
21
 
13
22
  export const FormContext = createContext(null);
14
23
 
@@ -40,7 +49,7 @@ export default function Form<T = any>({
40
49
  ...props
41
50
  }: FormProps<T>) {
42
51
  const {
43
- appInitValue,
52
+ // appInitValue,
44
53
  getFormItemValue,
45
54
  setInitData,
46
55
  setData,
@@ -54,7 +63,7 @@ export default function Form<T = any>({
54
63
  clearFormState,
55
64
  } = useFormStore(
56
65
  useShallow((state) => ({
57
- appInitValue: state.initialValues,
66
+ // appInitValue: state.initialValues,
58
67
  setInitData: state.setInitData,
59
68
  getFormValues: state.getFormValues,
60
69
  setFormState: state.setFormState,
@@ -68,7 +77,7 @@ export default function Form<T = any>({
68
77
  clearFormState: state.clearFormState,
69
78
 
70
79
  // Test, nhớ xóa sau khi xong
71
- formStates: state.formStates?.[formName],
80
+ // formStates: state.formStates?.[formName],
72
81
  })),
73
82
  );
74
83
 
@@ -165,8 +174,8 @@ export default function Form<T = any>({
165
174
 
166
175
  const resultValues = cloneDeep(formValues);
167
176
  const cleanValues = {} as T;
168
- uniq(listeners, (l) => l.name).forEach((l) => {
169
- 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));
170
179
  });
171
180
 
172
181
  const handleIsolateCase = async () => {
@@ -271,6 +280,7 @@ export default function Form<T = any>({
271
280
  setFormState({
272
281
  formName,
273
282
  isInitied: false,
283
+ submitState: SUBMIT_STATE.IDLE,
274
284
  }),
275
285
  );
276
286
  const totalListenerFields = getListeners();
@@ -403,9 +413,11 @@ export function useFormContext() {
403
413
  export function useForm<T = any>(
404
414
  formNameOrFormInstance?: string | PublicFormInstance<T>,
405
415
  ) {
406
- const targetFormName =
407
- typeof formNameOrFormInstance === "object" &&
408
- formNameOrFormInstance !== null
416
+ const formContext = useContext(FormContext);
417
+ const targetFormName = isNil(formNameOrFormInstance)
418
+ ? formContext?.formName
419
+ : typeof formNameOrFormInstance === "object" &&
420
+ formNameOrFormInstance !== null
409
421
  ? (formNameOrFormInstance as PublicFormInstance<T>).formName
410
422
  : (formNameOrFormInstance as string | undefined);
411
423
 
@@ -472,7 +484,33 @@ export const useFormStateWatch = <T = any,>(
472
484
  return formState as any;
473
485
  };
474
486
 
487
+ // Get Form Item State Using name (make sure no same name listener before using this) or formItemId, formNameOrFormInstance
488
+ // reutrn formItem state like isTouched, isDirty, errors
489
+ export const useFormItemStateWatch = <T = any,>(
490
+ nameOrFormItemId: string,
491
+ formNameOrFormInstance?: string | PublicFormInstance<T>,
492
+ ): UseFormItemStateWatchReturn => {
493
+ const [formInstance] = useForm<T>(formNameOrFormInstance as any);
494
+
495
+ const listener = useFormStore((state) => {
496
+ return state
497
+ .getListeners()
498
+ .find(
499
+ (l) =>
500
+ l.formName === formInstance?.formName &&
501
+ (l.name === nameOrFormItemId || l.formItemId === nameOrFormItemId),
502
+ );
503
+ });
504
+
505
+ return {
506
+ isTouched: listener?.isTouched,
507
+ isDirty: listener?.isDirty,
508
+ errors: listener?.internalErrors || [],
509
+ };
510
+ };
511
+
475
512
  Form.useForm = useForm;
476
513
  Form.useWatch = useWatch;
477
514
  Form.useSubmitDataWatch = useSubmitDataWatch;
478
515
  Form.useFormStateWatch = useFormStateWatch;
516
+ Form.useFormItemStateWatch = useFormItemStateWatch;
@@ -375,6 +375,7 @@ export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
375
375
  internalErrors,
376
376
  onFocus,
377
377
  emitFocus,
378
+ isInitied,
378
379
  }) {
379
380
  return storeSet(
380
381
  produce<any>((state: any) => {
@@ -412,17 +413,22 @@ export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
412
413
  storeListeners[findListenerIndex].emitFocus = emitFocus;
413
414
  }
414
415
 
416
+ if (!isNil(isInitied)) {
417
+ storeListeners[findListenerIndex].isInitied = isInitied;
418
+ }
419
+
415
420
  return;
416
421
  }
417
422
  storeListeners.push({
418
423
  name,
419
424
  formName,
420
- isTouched,
421
- isDirty,
425
+ isTouched: Boolean(isTouched),
426
+ isDirty: Boolean(isDirty),
422
427
  formItemId,
423
428
  internalErrors,
424
429
  onChange,
425
430
  onReset,
431
+ isInitied: Boolean(isInitied),
426
432
  });
427
433
  }),
428
434
  );
@@ -67,6 +67,12 @@ export interface UseFormItemReturn<T = any> {
67
67
  submitState?: SubmitState;
68
68
  }
69
69
 
70
+ export interface UseFormItemStateWatchReturn {
71
+ isTouched?: boolean;
72
+ isDirty?: boolean;
73
+ errors: FormFieldError[];
74
+ }
75
+
70
76
  export interface UseFormListProps<T = any> {
71
77
  name?: string;
72
78
  form?: PublicFormInstance<T>;