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

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
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
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.1] - 2026-01-22
19
+
20
+ - Fix UseFormItemControlReturn errors type to properly export FormFieldError[]
5
21
  ## [1.0.7-beta.1] - 2026-01-22
6
22
 
7
23
  - Add `FormFieldError` type export for typed error handling
@@ -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
@@ -1,6 +1,6 @@
1
1
  import { type RefObject } from "react";
2
2
  import type { FormInstance } from "../stores/formStore";
3
- import type { SubmitState, ValidationRule } from "../types/public";
3
+ import type { FormFieldError, SubmitState, ValidationRule } from "../types/public";
4
4
  type AnyObject = Record<string, any>;
5
5
  interface UseFormItemControlProps {
6
6
  formName?: string;
@@ -11,14 +11,16 @@ interface UseFormItemControlProps {
11
11
  rules?: ValidationRule[];
12
12
  elementRef?: RefObject<any> | null;
13
13
  }
14
- interface UseFormItemControlReturn {
14
+ export interface UseFormItemControlReturn {
15
15
  value: any;
16
16
  onChange: (value: any, options?: AnyObject) => void;
17
17
  state: any;
18
- errors: any;
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 {
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;
@@ -5,29 +5,40 @@ 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
@@ -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)) {
@@ -287,15 +299,28 @@ const useFormStateWatch = (formNameOrFormInstance) => {
287
299
  });
288
300
  return formState;
289
301
  };
302
+ const useFormItemStateWatch = (nameOrFormItemId, formNameOrFormInstance) => {
303
+ const [formInstance] = useForm(formNameOrFormInstance);
304
+ const listener = useFormStore((state) => {
305
+ return state.getListeners().find((l) => l.formName === (formInstance == null ? void 0 : formInstance.formName) && (l.name === nameOrFormItemId || l.formItemId === nameOrFormItemId));
306
+ });
307
+ return {
308
+ isTouched: listener == null ? void 0 : listener.isTouched,
309
+ isDirty: listener == null ? void 0 : listener.isDirty,
310
+ errors: (listener == null ? void 0 : listener.internalErrors) || []
311
+ };
312
+ };
290
313
  Form.useForm = useForm;
291
314
  Form.useWatch = useWatch;
292
315
  Form.useSubmitDataWatch = useSubmitDataWatch;
293
316
  Form.useFormStateWatch = useFormStateWatch;
317
+ Form.useFormItemStateWatch = useFormItemStateWatch;
294
318
  export {
295
319
  FormContext,
296
320
  Form as default,
297
321
  useForm,
298
322
  useFormContext,
323
+ useFormItemStateWatch,
299
324
  useFormStateWatch,
300
325
  useSubmitDataWatch,
301
326
  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.0",
3
+ "version": "1.0.8-beta.10",
4
4
  "description": "Lightweight React form management with list and listener support.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,6 +45,7 @@
45
45
  "eslint": "^9.14.0",
46
46
  "eslint-plugin-react-hooks": "^5.1.0",
47
47
  "eslint-plugin-react-refresh": "^0.4.14",
48
+ "framer-motion": "^12.29.0",
48
49
  "globals": "^15.12.0",
49
50
  "react": "^19.0.0",
50
51
  "react-dom": "^19.0.0",
package/src/App.tsx CHANGED
@@ -1,13 +1,19 @@
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
+
8
12
  const App = () => {
9
13
  const [form] = useForm("form1");
10
14
 
15
+ const watchCheckBox = Form.useWatch("checkControlledAfterInit", "form1");
16
+
11
17
  useEffect(() => {
12
18
  if (form) {
13
19
  // setTimeout(() => {
@@ -17,6 +23,20 @@ const App = () => {
17
23
  }, [form]);
18
24
  return (
19
25
  <div>
26
+ <AntdForm>
27
+ <motion.div
28
+ initial={{ opacity: 0 }}
29
+ animate={{ opacity: 1 }}
30
+ exit={{ opacity: 0 }}
31
+ transition={{ duration: 1 }}
32
+ >
33
+ <AntdForm.Item name={"234"} initialValue={"23432"} label="Antd Input">
34
+ <Input />
35
+ </AntdForm.Item>
36
+ </motion.div>
37
+ </AntdForm>
38
+
39
+ {/* Hidden Test */}
20
40
  <Form
21
41
  initialValues={{
22
42
  TestData: "",
@@ -27,13 +47,25 @@ const App = () => {
27
47
  console.log(values);
28
48
  }}
29
49
  formName={"form1"}
50
+ // hidden
30
51
  >
31
- <FormItem name={"TestData"}>
52
+ <FormItem
53
+ name={"username"}
54
+ rules={[
55
+ {
56
+ required: true,
57
+ message: "Test",
58
+ },
59
+ ]}
60
+ initialValue={"283746"}
61
+ // hidden
62
+ >
32
63
  <InputWrapper>
33
64
  <Input />
34
65
  </InputWrapper>
35
66
  </FormItem>
36
67
 
68
+ {/* Numberic test */}
37
69
  <FormItem
38
70
  name={"numericCode"}
39
71
  rules={[
@@ -48,6 +80,20 @@ const App = () => {
48
80
  <Input placeholder="Mã chỉ gồm số" style={{ width: 200 }} />
49
81
  </InputWrapper>
50
82
  </FormItem>
83
+
84
+ {/* Motion Test */}
85
+ <motion.div
86
+ initial={{ opacity: 0 }}
87
+ animate={{ opacity: 1 }}
88
+ exit={{ opacity: 0 }}
89
+ transition={{ duration: 1 }}
90
+ >
91
+ <FormItem name="motionTest" controlAfterInit initialValue={"1234134"}>
92
+ <InputWrapper>
93
+ <Input placeholder="Motion Test" style={{ width: 200 }} />
94
+ </InputWrapper>
95
+ </FormItem>
96
+ </motion.div>
51
97
  <FormList
52
98
  initialValues={[
53
99
  {
@@ -93,12 +139,40 @@ const App = () => {
93
139
  </div>
94
140
  )}
95
141
  </FormList>
142
+ <motion.div
143
+ initial={{ opacity: 0 }}
144
+ animate={{ opacity: 1 }}
145
+ exit={{ opacity: 0 }}
146
+ transition={{ duration: 1.5 }}
147
+ >
148
+ <FormItem
149
+ valuePropName="checked"
150
+ getValueFromEvent={(_, checked) => checked}
151
+ name="checkControlledAfterInit"
152
+ controlAfterInit={true}
153
+ initialValue={true}
154
+ >
155
+ <Checkbox />
156
+ </FormItem>
157
+ </motion.div>
158
+
159
+ <Button
160
+ onClick={() => {
161
+ const current = form?.getFieldValue("checkControlledAfterInit");
162
+ console.log("Toggle controlled after init: ", current);
163
+ form?.setFieldValue("checkControlledAfterInit", !current);
164
+ }}
165
+ >
166
+ Toggle
167
+ </Button>
96
168
  <Button htmlType="submit">Submit</Button>
97
169
  <Button
98
170
  onClick={() => {
99
171
  form?.resetFields?.();
100
172
  }}
101
- ></Button>
173
+ >
174
+ Reset
175
+ </Button>
102
176
  </Form>
103
177
  </div>
104
178
  );
@@ -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
  }
@@ -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)}
@@ -25,7 +25,11 @@ import {
25
25
  } from "../stores/formStore";
26
26
 
27
27
  import type { FormInstance } from "../stores/formStore";
28
- import type { SubmitState, ValidationRule } from "../types/public";
28
+ import type {
29
+ FormFieldError,
30
+ SubmitState,
31
+ ValidationRule,
32
+ } from "../types/public";
29
33
 
30
34
  type AnyObject = Record<string, any>;
31
35
 
@@ -39,14 +43,16 @@ interface UseFormItemControlProps {
39
43
  elementRef?: RefObject<any> | null;
40
44
  }
41
45
 
42
- interface UseFormItemControlReturn {
46
+ export interface UseFormItemControlReturn {
43
47
  value: any;
44
48
  onChange: (value: any, options?: AnyObject) => void;
45
49
  state: any;
46
- errors: any;
50
+ errors: FormFieldError[];
47
51
  onFocus: () => void;
48
52
  isDirty?: boolean;
53
+ isTouched?: boolean;
49
54
  submitState?: SubmitState;
55
+ isInitied?: boolean;
50
56
  }
51
57
 
52
58
  const VALID_PREMITIVE_TYPE = ["string", "number", "undefined"];
@@ -149,7 +155,7 @@ export default function useFormItemControl<T = any>({
149
155
  name,
150
156
  value,
151
157
  );
152
- onChange(value, { notTriggerDirty: true });
158
+ onChange(value, { notTriggerDirty: true, initiedData: true });
153
159
  };
154
160
 
155
161
  const onFocus = () => {
@@ -166,7 +172,13 @@ export default function useFormItemControl<T = any>({
166
172
  }
167
173
  };
168
174
 
169
- const onChange = (value: T, options?: any) => {
175
+ const onChange = (
176
+ value: T,
177
+ options?: {
178
+ notTriggerDirty?: boolean;
179
+ initiedData?: boolean;
180
+ },
181
+ ) => {
170
182
  if (options?.notTriggerDirty !== true) {
171
183
  setListener({
172
184
  formItemId,
@@ -174,6 +186,12 @@ export default function useFormItemControl<T = any>({
174
186
  isTouched: listener?.isTouched,
175
187
  });
176
188
  }
189
+ if (options?.initiedData === true) {
190
+ setListener({
191
+ formItemId,
192
+ isInitied: true,
193
+ });
194
+ }
177
195
  setData(formName || form?.formName || contextForm?.formName, name, value);
178
196
  };
179
197
 
@@ -183,10 +201,16 @@ export default function useFormItemControl<T = any>({
183
201
  value,
184
202
  getInitData(formName || form?.formName || contextForm?.formName, name),
185
203
  );
204
+ setListener({
205
+ formItemId,
206
+ isDirty: false,
207
+ isTouched: false,
208
+ });
186
209
  onChange(
187
210
  isNil(value)
188
211
  ? getInitData(formName || form?.formName || contextForm?.formName, name)
189
212
  : value,
213
+ { notTriggerDirty: true, initiedData: true },
190
214
  );
191
215
  };
192
216
 
@@ -517,7 +541,10 @@ export default function useFormItemControl<T = any>({
517
541
  onInitData(initialValue);
518
542
  }
519
543
  } else {
520
- onChange(internalInitValue, { notTriggerDirty: true });
544
+ onChange(internalInitValue, {
545
+ notTriggerDirty: true,
546
+ initiedData: true,
547
+ });
521
548
  }
522
549
  }
523
550
  return;
@@ -592,6 +619,8 @@ export default function useFormItemControl<T = any>({
592
619
  errors,
593
620
  onFocus,
594
621
  isDirty: listener?.isDirty,
622
+ isTouched: listener?.isTouched,
595
623
  submitState,
624
+ isInitied: listener?.isInitied,
596
625
  };
597
626
  }
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
 
@@ -5,10 +5,19 @@ 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
 
@@ -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();
@@ -472,7 +482,33 @@ export const useFormStateWatch = <T = any,>(
472
482
  return formState as any;
473
483
  };
474
484
 
485
+ // Get Form Item State Using name (make sure no same name listener before using this) or formItemId, formNameOrFormInstance
486
+ // reutrn formItem state like isTouched, isDirty, errors
487
+ export const useFormItemStateWatch = <T = any,>(
488
+ nameOrFormItemId: string,
489
+ formNameOrFormInstance?: string | PublicFormInstance<T>,
490
+ ): UseFormItemStateWatchReturn => {
491
+ const [formInstance] = useForm<T>(formNameOrFormInstance as any);
492
+
493
+ const listener = useFormStore((state) => {
494
+ return state
495
+ .getListeners()
496
+ .find(
497
+ (l) =>
498
+ l.formName === formInstance?.formName &&
499
+ (l.name === nameOrFormItemId || l.formItemId === nameOrFormItemId),
500
+ );
501
+ });
502
+
503
+ return {
504
+ isTouched: listener?.isTouched,
505
+ isDirty: listener?.isDirty,
506
+ errors: listener?.internalErrors || [],
507
+ };
508
+ };
509
+
475
510
  Form.useForm = useForm;
476
511
  Form.useWatch = useWatch;
477
512
  Form.useSubmitDataWatch = useSubmitDataWatch;
478
513
  Form.useFormStateWatch = useFormStateWatch;
514
+ 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>;